Skip to content

Commit

Permalink
Add & update docstrings, type hints, param defaults (#47)
Browse files Browse the repository at this point in the history
* Add, update docstrings, type hints, param defaults

* Update SDK readme's for multi-user clients

* Add doc example for filters
  • Loading branch information
MattTimms authored Nov 6, 2024
1 parent 0d1019e commit d88f261
Show file tree
Hide file tree
Showing 11 changed files with 362 additions and 71 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Redactive

The Redactive Application, Docs & Samples
The Redactive Application, Docs & Samples

## Directory Structure

Expand Down
52 changes: 52 additions & 0 deletions sdks/node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The library has following components.

- **AuthClient** - provides functionality to interact with data sources
- **SearchClient** - provides functionality to search chunks with Redactive search service in gRPC
- **MultiUserClient** - provides functionality manage multi-user search with Redactive search service

### AuthClient

Expand Down Expand Up @@ -76,6 +77,57 @@ const documentName = "AI Research Paper";
await client.queryChunksByDocumentName({ accessToken, documentName });
```

### Filters

Query methods, i.e. `queryChunks`, `queryChunksByDocumentName`, support a set of optional filters. The filters are applied in a logical 'AND' operation. If a data source provider does not support a filter-type, then no results from that provider are returned.

```typescript
import { Filters } from "@redactive/redactive/grpc/search";

// Query chunks from Confluence only, that are from documents created before last week, modified since last week,
// and that are from documents associated with a user's email. Include chunks from trashed documents.
const lastWeek = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
const filters: Filters = {
scope: ["confluence"],
created: {
before: lastWeek
},
modified: {
after: lastWeek
},
userEmails: ["[email protected]"],
includeContentInTrash: true
};
await client.queryChunks({ accessToken, semanticQuery, filters });
```

### Multi-User Client

The `MultiUserClient` class helps manage multiple users' authentication and access to the Redactive search service.

```typescript
import { MultiUserClient } from "@redactive/redactive";

const multiUserClient = MultiUserClient(
"REDACTIVE-API-KEY",
"https://example.com/callback/",
readUserData,
multiUserClient
);

// Present `connection_url` in browser for user to interact with:
const userId = "myUserId";
const connectionUrl = await multiUserClient.getBeginConnectionUrl(userId, "confluence");

// On user return from OAuth connection flow:
let [signInCode, state] = ["", ""]; // from URL query parameters
const isConnectionSuccessful = await multiUserClient.handleConnectionCallback(userId, signInCode, state);

// User can now use Redactive search service via `MultiUserClient`'s other methods:
const semanticQuery = "Tell me about the missing research vessel, the Borealis";
const chunks = await multiUserClient.queryChunks({ userId, semanticQuery });
```

## Development

The Node SDK code can be found the`sdks/node` directory in Redactive Github Repository.
Expand Down
25 changes: 25 additions & 0 deletions sdks/node/src/authClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export class AuthClient {
apiKey: string;
baseUrl: string = "https://api.redactive.ai";

/**
* Initialize the connection settings for the Redactive API.
* @param apiKey - The API key used for authentication.
* @param baseUrl - The base URL to the Redactive API.
*/
constructor(apiKey: string, baseUrl?: string) {
this.apiKey = apiKey;
this.baseUrl = baseUrl || this.baseUrl;
Expand All @@ -32,6 +37,15 @@ export class AuthClient {
});
}

/**
* Return a URL for authorizing Redactive to connect with provider on a user's behalf.
* @param provider - The name of the provider to connect with.
* @param redirectUri - THE URI to redirect to after initiating the connection. Defaults to an empty string.
* @param endpoint - The endpoint to use to access specific provider APIs. Only required if connecting to Zendesk. Defaults to None.
* @param codeParamAlias - The alias for the code parameter. This is the name of the query parameter that will need to be passed to the `/auth/token` endpoint as `code`. Defaults to None and will be `code` on the return.
* @param state - An optional parameter that is stored as app_callback_state for building callback url. Defaults to None.
* @returns The URL to redirect the user to for beginning the connection.
*/
async beginConnection({
provider,
redirectUri,
Expand All @@ -58,6 +72,12 @@ export class AuthClient {
return response.json().then((data) => data.url);
}

/**
* Exchange an authorization code and refresh token for access tokens.
* @param code - The authorization code received from the OAuth flow. Defaults to None.
* @param refreshToken - The refresh token used for token refreshing. Defaults to None.
* @returns an object containing access token and other token information.
*/
async exchangeTokens(code?: string, refreshToken?: string): Promise<ExchangeTokenResponse> {
if (!(code || refreshToken)) {
throw Error("Missing required data");
Expand All @@ -76,6 +96,11 @@ export class AuthClient {
return result as ExchangeTokenResponse;
}

/**
* Retrieve the list of user's provider connections.
* @param accessToken - the user's access token for authentication.
* @returns an object container the user ID and current provider connections.
*/
async listConnections(accessToken: string): Promise<UserConnections> {
const response = await fetch(`${this.baseUrl}/api/auth/connections`, {
method: "GET",
Expand Down
51 changes: 51 additions & 0 deletions sdks/node/src/multiUserClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ export class MultiUserClient {
readUserData: (userId: string) => Promise<UserData | undefined>;
writeUserData: (userId: string, data: UserData | undefined) => Promise<void>;

/**
* Redactive client handling multiple user authentication and access to the Redactive Search service.
* @param apiKey - Redactive API key.
* @param callbackUri - The URI to redirect to after initiating the connection.
* @param readUserData - Function to read user data from storage.
* @param writeUserData - Function to write user data to storage.
* @param options - An object of client options. Optional.
* @param options.authBaseUrl - Base URL for the authentication service. Optional.
* @param options.grpcHost - Host for the Redactive API service. Optional.
* @param options.grpcPort - Port for the Redactive API service. Optional.
*/
constructor(
apiKey: string,
callbackUri: string,
Expand All @@ -57,6 +68,12 @@ export class MultiUserClient {
this.writeUserData = writeUserData;
}

/**
* Return a URL for authorizing Redactive to connect with provider on a user's behalf.
* @param userId - A user ID to associate the connection URL with.
* @param provider - The name of the provider to connect with.
* @returns The URL to redirect the user to for beginning the connection.
*/
async getBeginConnectionUrl(userId: string, provider: string): Promise<string> {
const state = randomUUID();
const url = await this.authClient.beginConnection({ provider, redirectUri: this.callbackUri, state });
Expand Down Expand Up @@ -96,6 +113,14 @@ export class MultiUserClient {
return tokenBody.email;
}

/**
* The callback method for users completing the connection flow; to be called when user returns to app with
* connection-related URL query parameters.
* @param userId - The ID of the user completing their connection flow.
* @param signInCode - The connection sign-in code returned in the URL query parameters by completing the connection flow.
* @param state - The state value returned in the URL query parameters by completing the connection flow.
* @returns A boolean representing successful connection completion.
*/
async handleConnectionCallback(userId: string, signInCode: string, state: string): Promise<boolean> {
const userData = await this.readUserData(userId);
if (!userData || !state || userData.signInState !== state) {
Expand All @@ -105,6 +130,11 @@ export class MultiUserClient {
return true;
}

/**
* Retrieve the list of the user's provider connections.
* @param userId - The ID of the user.
* @returns A list of the user's connected providers.
*/
async getUserConnections(userId: string): Promise<string[]> {
let userData = await this.readUserData(userId);
if (!!userData && !!userData.idTokenExpiry && new Date(userData.idTokenExpiry) > new Date()) {
Expand All @@ -121,6 +151,14 @@ export class MultiUserClient {
await this.writeUserData(userId, undefined);
}

/**
* Query for relevant chunks based on a semantic query.
* @param userId - The ID of the user.
* @param semanticQuery - The query string used to find relevant chunks.
* @param count - The number of relevant chunks to retrieve. Defaults to 10.
* @param filters - An object of filters for querying. Optional.
* @returns list of relevant chunks.
*/
async queryChunks({ userId, semanticQuery, count = 10, filters }: QueryChunksParams): Promise<RelevantChunk[]> {
let userData = await this.readUserData(userId);
if (!userData || !userData.refreshToken) {
Expand All @@ -133,6 +171,13 @@ export class MultiUserClient {
return await this.searchClient.queryChunks({ accessToken: userData.idToken!, semanticQuery, count, filters });
}

/**
* Query for chunks by document name.
* @param userId - The ID of the user.
* @param documentName - The name of the document to retrieve chunks.
* @param filters - The filters for querying documents. Optional.
* @returns The complete list of chunks for the matching document.
*/
async queryChunksByDocumentName({
userId,
documentName,
Expand All @@ -149,6 +194,12 @@ export class MultiUserClient {
return await this.searchClient.queryChunksByDocumentName({ accessToken: userData.idToken!, documentName, filters });
}

/**
* Get chunks from a document by its URL.
* @param accessToken - The user's Redactive access token.
* @param url - The URL to the document for retrieving chunks.
* @returns The complete list of chunks for the matching document.
*/
async getChunksByUrl({ userId, url }: GetChunksByUrlParams): Promise<Chunk[]> {
let userData = await this.readUserData(userId);
if (!userData || !userData.refreshToken) {
Expand Down
26 changes: 26 additions & 0 deletions sdks/node/src/searchClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ export class SearchClient {
port: number = 443;
_cachedServiceClients: Map<string, Client>;

/**
* Redactive API search client.
* @param host - The hostname or IP address of the Redactive API service.
* @param port - The port number of the Redactive API service.
*/
constructor(host?: string, port?: number) {
this.host = host || this.host;
this.port = port || this.port;
Expand All @@ -55,6 +60,14 @@ export class SearchClient {
}
}

/**
* Query for relevant chunks based on a semantic query.
* @param accessToken - The user's Redactive access token.
* @param semanticQuery - The query string used to find relevant chunks.
* @param count - The number of relevant chunks to retrieve. Defaults to 10.
* @param filters - An object of filters for querying. Optional.
* @returns list of relevant chunks.
*/
async queryChunks({
accessToken,
semanticQuery,
Expand Down Expand Up @@ -87,6 +100,13 @@ export class SearchClient {
return response.relevantChunks;
}

/**
* Query for chunks by document name.
* @param accessToken - The user's Redactive access token.
* @param documentName - The name of the document to retrieve chunks.
* @param filters - The filters for querying documents. Optional.
* @returns The complete list of chunks for the matching document.
*/
async queryChunksByDocumentName({
accessToken,
documentName,
Expand Down Expand Up @@ -117,6 +137,12 @@ export class SearchClient {
return response.chunks;
}

/**
* Get chunks from a document by its URL.
* @param accessToken - The user's Redactive access token.
* @param url - The URL to the document for retrieving chunks.
* @returns The complete list of chunks for the matching document.
*/
async getChunksByUrl({ accessToken, url }: GetChunksByUrlSearchParams): Promise<Chunk[]> {
const requestMetadata = new Metadata();
requestMetadata.set("Authorization", `Bearer ${accessToken}`);
Expand Down
70 changes: 67 additions & 3 deletions sdks/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ python -m pip install .

## Usage

The library has following components.
The library has the following components:

- **AuthClient** - provides functionality to interact with data sources
- **SearchClient** - provides functionality to search chunks with Redactive search service in gRPC
- **SearchClient** - provides functionality to search chunks with Redactive search service
- **MultiUserClient** - provides functionality manage multi-user search with Redactive search service
- **RerankingSearchClient** [Experimental] - retrieves extra results, then re-ranks them using a more precise ranking function, returning the top_k results

### AuthClient
Expand Down Expand Up @@ -78,13 +79,76 @@ client.get_chunks_by_url(
url="https://example.com/document"
)

# Document Name Search : retrieve all chunks of a document identified by its name
# Document Name Search: retrieve all chunks of a document identified by its name
client.query_chunks_by_document_name(
access_token="REDACTIVE-USER-ACCESS-TOKEN",
document_name="Project Plan"
)
```

### Filters

Query methods, i.e. `query_chunks`, `query_chunks_by_document_name`, support a set of optional filters. The filters are applied in a logical 'AND' operation. If a data source provider does not support a filter-type, then no results from that provider are returned.

```python
from datetime import datetime, timedelta
from redactive.search_client import SearchClient
from redactive.grpc.v1 import Filters

client = SearchClient()

# Query chunks from Confluence only, that are from documents created before last week, modified since last week,
# and that are from documents associated with a user's email. Include chunks from trashed documents.
last_week = datetime.now() - timedelta(weeks=1)
filters = Filters().from_dict({
"scope": ["confluence"],
"created": {
"before": last_week,
},
"modified": {
"after": last_week,
},
"userEmails": ["[email protected]"],
"includeContentInTrash": True,
})
client.query_chunks(
access_token="REDACTIVE-USER-ACCESS-TOKEN",
semantic_query="Tell me about AI",
filters=filters
)
```

### Multi-User Client

The `MultiUserClient` class helps manage multiple users' authentication and access to the Redactive search service.

```python
from redactive.multi_user_client import MultiUserClient

multi_user_client = MultiUserClient(
api_key="REDACTIVE-API-KEY",
callback_uri="https://example.com/callback/",
read_user_data=...,
write_user_data=...,
)

# Present `connection_url` in browser for user to interact with:
user_id = ...
connection_url = await multi_user_client.get_begin_connection_url(user_id=user_id, provider="confluence")

# On user return from OAuth connection flow:
sign_in_code, state = ..., ... # from URL query parameters
is_connection_successful = await multi_user_client.handle_connection_callback(
user_id=user_id,
sign_in_code=sign_in_code,
state=state
)

# User can now use Redactive search service via `MultiUserClient`'s other methods:
semantic_query = "Tell me about the missing research vessel, the Borealis"
chunks = await multi_user_client.query_chunks(user_id=user_id, semantic_query=semantic_query)
```

## Development

The Python SDK code can be found the`sdks/python` directory in Redactive Github Repository.
Expand Down
Loading

0 comments on commit d88f261

Please sign in to comment.