Skip to content

Commit 3c375ef

Browse files
clinia-brandonadsheCopilot
authored
feat: [RET-2826] implement go client for sparse embedder (#28)
* feat: added client and stubs for testing * Update mock-server/grpc/stubs-generator/templates/py_templates/test_sparse_embedder_mock_server.py.jinja Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update mock-server/grpc/stubs-generator/Makefile Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * feat: add sparse embedder implementation * feat: add stub template for go * feat: add tests * feat: fix tests * feat: fix tests * Update mock-server/grpc/stubs-generator/stubs_generator/sparse_embedder.py Co-authored-by: clinia-brando <brando.tovar@clinia.com> * fix: changes requested in PR 27 * Update clients/models-client-python/models_client_python/sparse_embedder.py Co-authored-by: clinia-brando <brando.tovar@clinia.com> * Update clients/models-client-python/tests/test_sparse_embedder.py Co-authored-by: clinia-brando <brando.tovar@clinia.com> * fix: changes requested in PR 27 * feat: fix tests * feat: add readme * feat: add nil test * feat: add sparse embedder nil test * feat: add nil check * feat: resolve comments * feat: [RET-2823] write client for sparse embedder in typescript (#30) * feat: add ts client * feat: add tests * feat: fix tests * feat: generate test * feat: generate test * feat: fix tests * feat: adds nul test * feat: add sparse embedder example * feat: sparse embedder --------- Co-authored-by: Nadia <nadia.sheikh@clinia.com> Co-authored-by: Nadia Sheikh <144166074+nadshe@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 10663d0 commit 3c375ef

File tree

15 files changed

+343
-6
lines changed

15 files changed

+343
-6
lines changed

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,44 @@ async function runEmbedderExample() {
7474
runEmbedderExample().catch(console.error);
7575
```
7676

77+
### Sparse Embedder
78+
79+
```typescript
80+
import { sparseEmbedder } from '@clinia/models-client-sparse-embedder';
81+
82+
async function runEmbedderExample() {
83+
const myEmbedder = sparseEmbedder({
84+
host: {
85+
Url: '127.0.0.1',
86+
Scheme: 'http',
87+
Port: 8001,
88+
},
89+
});
90+
91+
// Will throw an error if server is not ready
92+
await myEmbedder.requester.health();
93+
94+
// Will throw an error if model is not ready
95+
await myEmbedder.ready(
96+
process.env.CLINIA_MODEL_NAME,
97+
process.env.CLINIA_MODEL_VERSION,
98+
);
99+
100+
const result = await myEmbedder.embed(
101+
process.env.CLINIA_MODEL_NAME,
102+
process.env.CLINIA_MODEL_VERSION,
103+
{
104+
texts: ['Clinia is based in Montreal'],
105+
id: 'request-123',
106+
},
107+
);
108+
109+
console.log(JSON.stringify(result, null, 2));
110+
}
111+
112+
runEmbedderExample().catch(console.error);
113+
```
114+
77115
### Chunker
78116

79117
```typescript

packages/models-client-common/src/output.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,29 @@ export const getOutputFp32Contents = (output: Output): Float32Array[] => {
2121
return contents;
2222
};
2323

24+
export const getOutputSparseContents = (
25+
output: Output,
26+
): Record<string, number>[] => {
27+
if (output.datatype !== 'BYTES') {
28+
throw new Error('Data type not supported');
29+
}
30+
31+
const contents: Record<string, number>[] = [];
32+
for (const content of output.contents) {
33+
if (content.stringContents) {
34+
for (const jsonString of content.stringContents) {
35+
try {
36+
contents.push(JSON.parse(jsonString));
37+
} catch (err: any) {
38+
throw new Error(`Failed to parse JSON string: ${err.message}`);
39+
}
40+
}
41+
}
42+
}
43+
44+
return contents;
45+
};
46+
2447
export const getOutputStringContents = (output: Output): string[][] => {
2548
if (output.datatype !== 'BYTES') {
2649
throw new Error('Data type not supported');
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../LICENSE
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { RequesterConfig } from '@clinia/models-client-common';
2+
import { SparseEmbedder } from '../src/sparse-embedder';
3+
import { createGrpcRequester } from '@clinia/models-requester-grpc';
4+
5+
export const sparseEmbedder = (options: RequesterConfig): SparseEmbedder => {
6+
return new SparseEmbedder({
7+
requester: createGrpcRequester(options),
8+
});
9+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { RequesterConfig } from '@clinia/models-client-common';
2+
import { SparseEmbedder } from '../src/sparse-embedder';
3+
import { createGrpcRequester } from '@clinia/models-requester-grpc';
4+
5+
export const sparseEmbedder = (options: RequesterConfig): SparseEmbedder => {
6+
return new SparseEmbedder({
7+
requester: createGrpcRequester(options),
8+
});
9+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// eslint-disable-next-line import/no-unresolved
2+
export * from './dist/builds/node';
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// eslint-disable-next-line import/no-commonjs,import/extensions
2+
module.exports = require('./dist/models-client-sparse-embedder.cjs');
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"name": "@clinia/models-client-sparse-embedder",
3+
"version": "1.0.0",
4+
"description": "Javascript client for Clinia sparse embed model",
5+
"repository": {
6+
"type": "git",
7+
"url": "git+https://github.com/clinia/models-client-javascript.git"
8+
},
9+
"license": "MIT",
10+
"author": "Clinia",
11+
"type": "module",
12+
"exports": {
13+
".": {
14+
"node": {
15+
"types": {
16+
"import": "./dist/node.d.ts",
17+
"module": "./dist/node.d.ts",
18+
"require": "./dist/node.d.cts"
19+
},
20+
"import": "./dist/builds/node.js",
21+
"module": "./dist/builds/node.js",
22+
"require": "./dist/builds/node.cjs"
23+
},
24+
"default": {
25+
"types": "./dist/browser.d.ts",
26+
"module": "./dist/builds/browser.js",
27+
"import": "./dist/builds/browser.js",
28+
"default": "./dist/builds/browser.umd.js"
29+
}
30+
},
31+
"./dist/builds/*": "./dist/builds/*.js"
32+
},
33+
"jsdelivr": "./dist/builds/browser.umd.js",
34+
"unpkg": "./dist/builds/browser.umd.js",
35+
"react-native": "./dist/builds/browser.js",
36+
"files": [
37+
"dist",
38+
"index.js",
39+
"index.d.ts"
40+
],
41+
"scripts": {
42+
"build": "yarn clean && yarn tsup",
43+
"clean": "rm -rf ./dist || true",
44+
"test": "tsc --noEmit && vitest --run",
45+
"test:bundle": "publint . && attw --pack ."
46+
},
47+
"dependencies": {
48+
"@clinia/models-client-common": "1.0.0",
49+
"@clinia/models-requester-grpc": "1.0.0"
50+
},
51+
"devDependencies": {
52+
"@arethetypeswrong/cli": "0.17.3",
53+
"@types/node": "22.10.7",
54+
"publint": "0.3.2",
55+
"rollup": "4.30.1",
56+
"tsup": "8.3.5",
57+
"typescript": "5.7.3",
58+
"vitest": "^3.0.5"
59+
},
60+
"engines": {
61+
"node": ">= 14.0.0"
62+
}
63+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import {
2+
type ClientOptions,
3+
type Requester,
4+
type Input,
5+
type Datatype,
6+
getOutputSparseContents,
7+
} from '@clinia/models-client-common';
8+
9+
export type SparseEmbedRequest = {
10+
/**
11+
* The unique identifier for the request.
12+
*/
13+
id: string;
14+
/**
15+
* The list of texts to be embedded.
16+
*/
17+
texts: string[];
18+
};
19+
20+
export type SparseEmbedResponse = {
21+
/**
22+
* The unique identifier for the response,corresponding to that of the request.
23+
*/
24+
id: string;
25+
/**
26+
* The list of sparse embeddings for each text. Each embedding is a map from feature names to their corresponding values.
27+
*/
28+
embeddings: Record<string, number>[];
29+
};
30+
31+
const SPARSE_EMBEDDER_INPUT_KEY = 'text';
32+
const SPARSE_EMBEDDER_OUTPUT_KEY = 'embedding';
33+
const SPARSE_EMBEDDER_INPUT_DATATYPE: Datatype = 'BYTES';
34+
35+
export class SparseEmbedder {
36+
private _requester: Requester;
37+
38+
/**
39+
* Get the underlying requester instance.
40+
*/
41+
get requester(): Requester {
42+
return this._requester;
43+
}
44+
45+
/**
46+
* Creates an instance of SparseEmbedder.
47+
* @param options - The client options containing the requester.
48+
*/
49+
constructor(options: ClientOptions) {
50+
this._requester = options.requester;
51+
}
52+
53+
/**
54+
* Asynchronously generate embeddings using a specified model.
55+
* @param modelName - The name of the model to use.
56+
* @param modelVersion - The version of the model to use.
57+
* @param request - The request containing texts to be embedded.
58+
* @returns The response containing the embeddings.
59+
*/
60+
async embed(
61+
modelName: string,
62+
modelVersion: string,
63+
request: SparseEmbedRequest,
64+
): Promise<SparseEmbedResponse> {
65+
if (!request.texts) {
66+
throw new Error('Request texts must be provided.');
67+
}
68+
69+
if (request.texts.length === 0) {
70+
throw new Error('Request texts cannot be empty.');
71+
}
72+
73+
const inputs: Input[] = [
74+
{
75+
name: SPARSE_EMBEDDER_INPUT_KEY,
76+
shape: [request.texts.length],
77+
datatype: SPARSE_EMBEDDER_INPUT_DATATYPE,
78+
contents: [
79+
{
80+
stringContents: request.texts,
81+
},
82+
],
83+
},
84+
];
85+
86+
// The embedder model has only one input and one output
87+
const outputKeys = [SPARSE_EMBEDDER_OUTPUT_KEY];
88+
89+
const outputs = await this._requester.infer(
90+
modelName,
91+
modelVersion,
92+
inputs,
93+
outputKeys,
94+
request.id,
95+
);
96+
97+
const embeddings = getOutputSparseContents(outputs[0]);
98+
99+
return {
100+
id: request.id,
101+
embeddings,
102+
};
103+
}
104+
105+
/**
106+
* Checks the readiness status of the model.
107+
* @throws {Error} If the model is not ready.
108+
*/
109+
async ready(modelName: string, modelVersion: string): Promise<void> {
110+
await this._requester.ready(modelName, modelVersion);
111+
}
112+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"extends": "../../tsconfig.json",
3+
"compilerOptions": {
4+
"types": ["node", "vitest/globals"],
5+
"outDir": "dist",
6+
"skipLibCheck": true
7+
},
8+
"include": ["src", "model", "builds"],
9+
"exclude": ["dist", "node_modules"]
10+
}

0 commit comments

Comments
 (0)