lamhieu commited on
Commit
e8f9d10
·
1 Parent(s): fce0b1f

chore: initialize the project

Browse files
.editorconfig ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # top-most EditorConfig file
2
+ root = true
3
+
4
+ # Unix-style newlines with a newline ending every file
5
+ [*]
6
+ end_of_line = lf
7
+ insert_final_newline = true
8
+
9
+ # Matches multiple files with brace expansion notation
10
+ # Set default charset
11
+ [*]
12
+ charset = utf-8
13
+ indent_style = space
14
+ indent_size = 2
.github/workflows/githubhfsync.yaml ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync Repository to HuggingFace Space
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch: # Enable manual trigger
7
+
8
+ jobs:
9
+ sync-to-huggingface:
10
+ name: Sync code to HuggingFace Space
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - name: Checkout repository
14
+ uses: actions/checkout@v3
15
+ with:
16
+ fetch-depth: 0 # Fetch all history for all branches and tags
17
+ lfs: true # Enable Git LFS support
18
+
19
+ - name: Push to HuggingFace Space
20
+ env:
21
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
22
+ run: |
23
+ if ! git push https://lamhieu:$HF_TOKEN@huggingface.co/spaces/lamhieu/lightweight-embeddings main; then
24
+ echo "Failed to sync with HuggingFace Space"
25
+ exit 1
26
+ fi
.gitignore ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python bytecode files
2
+ # Ignore Python bytecode files
3
+ *.pyc
4
+
5
+ # Distribution packages
6
+ # Ignore distribution packages
7
+ /dist/*
8
+
9
+ # Test and coverage reports
10
+ # Ignore coverage and test result files
11
+ .coverage
12
+ .pytest_cache
13
+ .mypy_cache
14
+
15
+ # Log and temporary files
16
+ # Ignore log files and temporary files
17
+ *.log
18
+ *.tmp
19
+ tmp
20
+
21
+ # System files
22
+ # Ignore OS generated files
23
+ .DS_Store
24
+
25
+ # IDE and editor specific files
26
+ # Ignore project-specific files from various IDEs and editors
27
+ .idea/*
28
+ .vscode/*
29
+ .python-version
30
+
31
+ # Generated documentation
32
+ # Ignore generated documentation files
33
+ /docs/site/*
34
+
35
+ # Virtual environments
36
+ # Ignore virtual environment directories
37
+ .venv
38
+
39
+ # Configuration files
40
+ # Ignore configuration files
41
+ .poetry.toml
42
+ .env.local
43
+ .env.development
44
+ .env.test
45
+ .env.production
46
+ .env
47
+
48
+ # Temporary files and directories for operations
49
+ # Ignore Ops temporary files and directories
50
+ .aider*
51
+
52
+ # Credentials and secrets
53
+ # Ignore credentials and secrets files
54
+ .credentials
.pylintrc ADDED
@@ -0,0 +1,579 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [MAIN]
2
+
3
+ # Specify a configuration file.
4
+ #rcfile=
5
+
6
+ # Python code to execute, usually for sys.path manipulation such as
7
+ # pygtk.require().
8
+ #init-hook=
9
+
10
+ # Files or directories to be skipped. They should be base names, not
11
+ # paths.
12
+ ignore=CVS
13
+
14
+ # Add files or directories matching the regex patterns to the ignore-list. The
15
+ # regex matches against paths and can be in Posix or Windows format.
16
+ ignore-paths=
17
+
18
+ # Files or directories matching the regex patterns are skipped. The regex
19
+ # matches against base names, not paths.
20
+ ignore-patterns=^\.#
21
+
22
+ # Pickle collected data for later comparisons.
23
+ persistent=yes
24
+
25
+ # List of plugins (as comma separated values of python modules names) to load,
26
+ # usually to register additional checkers.
27
+ load-plugins=
28
+ pylint.extensions.check_elif,
29
+ pylint.extensions.bad_builtin,
30
+ pylint.extensions.docparams,
31
+ pylint.extensions.for_any_all,
32
+ pylint.extensions.set_membership,
33
+ pylint.extensions.code_style,
34
+ pylint.extensions.overlapping_exceptions,
35
+ pylint.extensions.typing,
36
+ pylint.extensions.redefined_variable_type,
37
+ pylint.extensions.comparison_placement,
38
+ pylint.extensions.mccabe,
39
+
40
+ # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
41
+ # number of processors available to use.
42
+ jobs=0
43
+
44
+ # When enabled, pylint would attempt to guess common misconfiguration and emit
45
+ # user-friendly hints instead of false-positive error messages.
46
+ suggestion-mode=yes
47
+
48
+ # Allow loading of arbitrary C extensions. Extensions are imported into the
49
+ # active Python interpreter and may run arbitrary code.
50
+ unsafe-load-any-extension=no
51
+
52
+ # A comma-separated list of package or module names from where C extensions may
53
+ # be loaded. Extensions are loading into the active Python interpreter and may
54
+ # run arbitrary code
55
+ extension-pkg-allow-list=
56
+
57
+ # Minimum supported python version
58
+ py-version = 3.7.2
59
+
60
+ # Control the amount of potential inferred values when inferring a single
61
+ # object. This can help the performance when dealing with large functions or
62
+ # complex, nested conditions.
63
+ limit-inference-results=100
64
+
65
+ # Specify a score threshold to be exceeded before program exits with error.
66
+ fail-under=10.0
67
+
68
+ # Return non-zero exit code if any of these messages/categories are detected,
69
+ # even if score is above --fail-under value. Syntax same as enable. Messages
70
+ # specified are enabled, while categories only check already-enabled messages.
71
+ fail-on=
72
+
73
+
74
+ [MESSAGES CONTROL]
75
+
76
+ # Only show warnings with the listed confidence levels. Leave empty to show
77
+ # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
78
+ # confidence=
79
+
80
+ # Enable the message, report, category or checker with the given id(s). You can
81
+ # either give multiple identifier separated by comma (,) or put this option
82
+ # multiple time (only on the command line, not in the configuration file where
83
+ # it should appear only once). See also the "--disable" option for examples.
84
+ enable=
85
+ use-symbolic-message-instead,
86
+ useless-suppression,
87
+
88
+ # Disable the message, report, category or checker with the given id(s). You
89
+ # can either give multiple identifiers separated by comma (,) or put this
90
+ # option multiple times (only on the command line, not in the configuration
91
+ # file where it should appear only once).You can also use "--disable=all" to
92
+ # disable everything first and then re-enable specific checks. For example, if
93
+ # you want to run only the similarities checker, you can use "--disable=all
94
+ # --enable=similarities". If you want to run only the classes checker, but have
95
+ # no Warning level messages displayed, use"--disable=all --enable=classes
96
+ # --disable=W"
97
+
98
+ disable=
99
+ attribute-defined-outside-init,
100
+ invalid-name,
101
+ missing-docstring,
102
+ protected-access,
103
+ too-few-public-methods,
104
+ # handled by black
105
+ format,
106
+ # We anticipate #3512 where it will become optional
107
+ fixme,
108
+ cyclic-import,
109
+ import-error,
110
+ #
111
+ unnecessary-pass,
112
+ unrecognized-option,
113
+ cell-var-from-loop,
114
+ no-member,
115
+ wrong-import-order,
116
+ raise-missing-from,
117
+ consider-using-f-string
118
+
119
+
120
+ [REPORTS]
121
+
122
+ # Set the output format. Available formats are text, parseable, colorized, msvs
123
+ # (visual studio) and html. You can also give a reporter class, eg
124
+ # mypackage.mymodule.MyReporterClass.
125
+ output-format=text
126
+
127
+ # Tells whether to display a full report or only the messages
128
+ reports=no
129
+
130
+ # Python expression which should return a note less than 10 (10 is the highest
131
+ # note). You have access to the variables 'fatal', 'error', 'warning', 'refactor', 'convention'
132
+ # and 'info', which contain the number of messages in each category, as
133
+ # well as 'statement', which is the total number of statements analyzed. This
134
+ # score is used by the global evaluation report (RP0004).
135
+ evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
136
+
137
+ # Template used to display messages. This is a python new-style format string
138
+ # used to format the message information. See doc for all details
139
+ #msg-template=
140
+
141
+ # Activate the evaluation score.
142
+ score=yes
143
+
144
+
145
+ [LOGGING]
146
+
147
+ # Logging modules to check that the string format arguments are in logging
148
+ # function parameter format
149
+ logging-modules=logging
150
+
151
+ # The type of string formatting that logging methods do. `old` means using %
152
+ # formatting, `new` is for `{}` formatting.
153
+ logging-format-style=old
154
+
155
+
156
+ [MISCELLANEOUS]
157
+
158
+ # List of note tags to take in consideration, separated by a comma.
159
+ notes=FIXME,XXX,TODO
160
+
161
+ # Regular expression of note tags to take in consideration.
162
+ #notes-rgx=
163
+
164
+
165
+ [SIMILARITIES]
166
+
167
+ # Minimum lines number of a similarity.
168
+ min-similarity-lines=6
169
+
170
+ # Ignore comments when computing similarities.
171
+ ignore-comments=yes
172
+
173
+ # Ignore docstrings when computing similarities.
174
+ ignore-docstrings=yes
175
+
176
+ # Ignore imports when computing similarities.
177
+ ignore-imports=yes
178
+
179
+ # Signatures are removed from the similarity computation
180
+ ignore-signatures=yes
181
+
182
+
183
+ [VARIABLES]
184
+
185
+ # Tells whether we should check for unused import in __init__ files.
186
+ init-import=no
187
+
188
+ # A regular expression matching the name of dummy variables (i.e. expectedly
189
+ # not used).
190
+ dummy-variables-rgx=_$|dummy
191
+
192
+ # List of additional names supposed to be defined in builtins. Remember that
193
+ # you should avoid defining new builtins when possible.
194
+ additional-builtins=
195
+
196
+ # List of strings which can identify a callback function by name. A callback
197
+ # name must start or end with one of those strings.
198
+ callbacks=cb_,_cb
199
+
200
+ # Tells whether unused global variables should be treated as a violation.
201
+ allow-global-unused-variables=yes
202
+
203
+ # List of names allowed to shadow builtins
204
+ allowed-redefined-builtins=
205
+
206
+ # Argument names that match this expression will be ignored. Default to name
207
+ # with leading underscore.
208
+ ignored-argument-names=_.*
209
+
210
+ # List of qualified module names which can have objects that can redefine
211
+ # builtins.
212
+ redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
213
+
214
+
215
+ [FORMAT]
216
+
217
+ # Maximum number of characters on a single line.
218
+ max-line-length=120
219
+
220
+ # Regexp for a line that is allowed to be longer than the limit.
221
+ ignore-long-lines=^\s*(# )?<?https?://\S+>?$
222
+
223
+ # Allow the body of an if to be on the same line as the test if there is no
224
+ # else.
225
+ single-line-if-stmt=no
226
+
227
+ # Allow the body of a class to be on the same line as the declaration if body
228
+ # contains single statement.
229
+ single-line-class-stmt=no
230
+
231
+ # Maximum number of lines in a module
232
+ max-module-lines=1000
233
+
234
+ # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
235
+ # tab).
236
+ indent-string=' '
237
+
238
+ # Number of spaces of indent required inside a hanging or continued line.
239
+ indent-after-paren=4
240
+
241
+ # Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
242
+ expected-line-ending-format=
243
+
244
+
245
+ [BASIC]
246
+
247
+ # Good variable names which should always be accepted, separated by a comma
248
+ good-names=i,j,k,ex,Run,_
249
+
250
+ # Good variable names regexes, separated by a comma. If names match any regex,
251
+ # they will always be accepted
252
+ good-names-rgxs=
253
+
254
+ # Bad variable names which should always be refused, separated by a comma
255
+ bad-names=foo,bar,baz,toto,tutu,tata
256
+
257
+ # Bad variable names regexes, separated by a comma. If names match any regex,
258
+ # they will always be refused
259
+ bad-names-rgxs=
260
+
261
+ # Colon-delimited sets of names that determine each other's naming style when
262
+ # the name regexes allow several styles.
263
+ name-group=
264
+
265
+ # Include a hint for the correct naming format with invalid-name
266
+ include-naming-hint=no
267
+
268
+ # Naming style matching correct function names.
269
+ function-naming-style=snake_case
270
+
271
+ # Regular expression matching correct function names
272
+ function-rgx=[a-z_][a-z0-9_]{2,30}$
273
+
274
+ # Naming style matching correct variable names.
275
+ variable-naming-style=snake_case
276
+
277
+ # Regular expression matching correct variable names
278
+ variable-rgx=[a-z_][a-z0-9_]{2,30}$
279
+
280
+ # Naming style matching correct constant names.
281
+ const-naming-style=UPPER_CASE
282
+
283
+ # Regular expression matching correct constant names
284
+ const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
285
+
286
+ # Naming style matching correct attribute names.
287
+ attr-naming-style=snake_case
288
+
289
+ # Regular expression matching correct attribute names
290
+ attr-rgx=[a-z_][a-z0-9_]{2,}$
291
+
292
+ # Naming style matching correct argument names.
293
+ argument-naming-style=snake_case
294
+
295
+ # Regular expression matching correct argument names
296
+ argument-rgx=[a-z_][a-z0-9_]{2,30}$
297
+
298
+ # Naming style matching correct class attribute names.
299
+ class-attribute-naming-style=any
300
+
301
+ # Regular expression matching correct class attribute names
302
+ class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
303
+
304
+ # Naming style matching correct class constant names.
305
+ class-const-naming-style=UPPER_CASE
306
+
307
+ # Regular expression matching correct class constant names. Overrides class-
308
+ # const-naming-style.
309
+ #class-const-rgx=
310
+
311
+ # Naming style matching correct inline iteration names.
312
+ inlinevar-naming-style=any
313
+
314
+ # Regular expression matching correct inline iteration names
315
+ inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
316
+
317
+ # Naming style matching correct class names.
318
+ class-naming-style=PascalCase
319
+
320
+ # Regular expression matching correct class names
321
+ class-rgx=[A-Z_][a-zA-Z0-9]+$
322
+
323
+
324
+ # Naming style matching correct module names.
325
+ module-naming-style=snake_case
326
+
327
+ # Regular expression matching correct module names
328
+ module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
329
+
330
+
331
+ # Naming style matching correct method names.
332
+ method-naming-style=snake_case
333
+
334
+ # Regular expression matching correct method names
335
+ method-rgx=[a-z_][a-z0-9_]{2,}$
336
+
337
+ # Regular expression which can overwrite the naming style set by typevar-naming-style.
338
+ #typevar-rgx=
339
+
340
+ # Regular expression which should only match function or class names that do
341
+ # not require a docstring. Use ^(?!__init__$)_ to also check __init__.
342
+ no-docstring-rgx=__.*__
343
+
344
+ # Minimum line length for functions/classes that require docstrings, shorter
345
+ # ones are exempt.
346
+ docstring-min-length=-1
347
+
348
+ # List of decorators that define properties, such as abc.abstractproperty.
349
+ property-classes=abc.abstractproperty
350
+
351
+
352
+ [TYPECHECK]
353
+
354
+ # Regex pattern to define which classes are considered mixins if ignore-mixin-
355
+ # members is set to 'yes'
356
+ mixin-class-rgx=.*MixIn
357
+
358
+ # List of module names for which member attributes should not be checked
359
+ # (useful for modules/projects where namespaces are manipulated during runtime
360
+ # and thus existing member attributes cannot be deduced by static analysis). It
361
+ # supports qualified module names, as well as Unix pattern matching.
362
+ ignored-modules=
363
+
364
+ # List of class names for which member attributes should not be checked (useful
365
+ # for classes with dynamically set attributes). This supports the use of
366
+ # qualified names.
367
+ ignored-classes=SQLObject, optparse.Values, thread._local, _thread._local
368
+
369
+ # List of members which are set dynamically and missed by pylint inference
370
+ # system, and so shouldn't trigger E1101 when accessed. Python regular
371
+ # expressions are accepted.
372
+ generated-members=REQUEST,acl_users,aq_parent,argparse.Namespace
373
+
374
+ # List of decorators that create context managers from functions, such as
375
+ # contextlib.contextmanager.
376
+ contextmanager-decorators=contextlib.contextmanager
377
+
378
+ # Tells whether to warn about missing members when the owner of the attribute
379
+ # is inferred to be None.
380
+ ignore-none=yes
381
+
382
+ # This flag controls whether pylint should warn about no-member and similar
383
+ # checks whenever an opaque object is returned when inferring. The inference
384
+ # can return multiple potential results while evaluating a Python object, but
385
+ # some branches might not be evaluated, which results in partial inference. In
386
+ # that case, it might be useful to still emit no-member and other checks for
387
+ # the rest of the inferred objects.
388
+ ignore-on-opaque-inference=yes
389
+
390
+ # Show a hint with possible names when a member name was not found. The aspect
391
+ # of finding the hint is based on edit distance.
392
+ missing-member-hint=yes
393
+
394
+ # The minimum edit distance a name should have in order to be considered a
395
+ # similar match for a missing member name.
396
+ missing-member-hint-distance=1
397
+
398
+ # The total number of similar names that should be taken in consideration when
399
+ # showing a hint for a missing member.
400
+ missing-member-max-choices=1
401
+
402
+ [SPELLING]
403
+
404
+ # Spelling dictionary name. Available dictionaries: none. To make it working
405
+ # install python-enchant package.
406
+ spelling-dict=
407
+
408
+ # List of comma separated words that should not be checked.
409
+ spelling-ignore-words=
410
+
411
+ # List of comma separated words that should be considered directives if they
412
+ # appear and the beginning of a comment and should not be checked.
413
+ spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy:,pragma:,# noinspection
414
+
415
+ # A path to a file that contains private dictionary; one word per line.
416
+ spelling-private-dict-file=.pyenchant_pylint_custom_dict.txt
417
+
418
+ # Tells whether to store unknown words to indicated private dictionary in
419
+ # --spelling-private-dict-file option instead of raising a message.
420
+ spelling-store-unknown-words=no
421
+
422
+ # Limits count of emitted suggestions for spelling mistakes.
423
+ max-spelling-suggestions=2
424
+
425
+
426
+ [DESIGN]
427
+
428
+ # Maximum number of arguments for function / method
429
+ max-args=10
430
+
431
+ # Maximum number of locals for function / method body
432
+ max-locals=25
433
+
434
+ # Maximum number of return / yield for function / method body
435
+ max-returns=11
436
+
437
+ # Maximum number of branch for function / method body
438
+ max-branches=27
439
+
440
+ # Maximum number of statements in function / method body
441
+ max-statements=100
442
+
443
+ # Maximum number of parents for a class (see R0901).
444
+ max-parents=7
445
+
446
+ # List of qualified class names to ignore when counting class parents (see R0901).
447
+ ignored-parents=
448
+
449
+ # Maximum number of attributes for a class (see R0902).
450
+ max-attributes=11
451
+
452
+ # Minimum number of public methods for a class (see R0903).
453
+ min-public-methods=2
454
+
455
+ # Maximum number of public methods for a class (see R0904).
456
+ max-public-methods=25
457
+
458
+ # Maximum number of boolean expressions in an if statement (see R0916).
459
+ max-bool-expr=5
460
+
461
+ # List of regular expressions of class ancestor names to
462
+ # ignore when counting public methods (see R0903).
463
+ exclude-too-few-public-methods=
464
+
465
+ max-complexity=10
466
+
467
+ [CLASSES]
468
+
469
+ # List of method names used to declare (i.e. assign) instance attributes.
470
+ defining-attr-methods=__init__,__new__,setUp,__post_init__
471
+
472
+ # List of valid names for the first argument in a class method.
473
+ valid-classmethod-first-arg=cls
474
+
475
+ # List of valid names for the first argument in a metaclass class method.
476
+ valid-metaclass-classmethod-first-arg=mcs
477
+
478
+ # List of member names, which should be excluded from the protected access
479
+ # warning.
480
+ exclude-protected=_asdict,_fields,_replace,_source,_make
481
+
482
+ # Warn about protected attribute access inside special methods
483
+ check-protected-access-in-special-methods=no
484
+
485
+ [IMPORTS]
486
+
487
+ # List of modules that can be imported at any level, not just the top level
488
+ # one.
489
+ allow-any-import-level=
490
+
491
+ # Allow wildcard imports from modules that define __all__.
492
+ allow-wildcard-with-all=no
493
+
494
+ # Analyse import fallback blocks. This can be used to support both Python 2 and
495
+ # 3 compatible code, which means that the block might have code that exists
496
+ # only in one or another interpreter, leading to false positives when analysed.
497
+ analyse-fallback-blocks=no
498
+
499
+ # Deprecated modules which should not be used, separated by a comma
500
+ deprecated-modules=regsub,TERMIOS,Bastion,rexec
501
+
502
+ # Create a graph of every (i.e. internal and external) dependencies in the
503
+ # given file (report RP0402 must not be disabled)
504
+ import-graph=
505
+
506
+ # Create a graph of external dependencies in the given file (report RP0402 must
507
+ # not be disabled)
508
+ ext-import-graph=
509
+
510
+ # Create a graph of internal dependencies in the given file (report RP0402 must
511
+ # not be disabled)
512
+ int-import-graph=
513
+
514
+ # Force import order to recognize a module as part of the standard
515
+ # compatibility libraries.
516
+ known-standard-library=
517
+
518
+ # Force import order to recognize a module as part of a third party library.
519
+ known-third-party=enchant
520
+
521
+ # Couples of modules and preferred modules, separated by a comma.
522
+ preferred-modules=
523
+
524
+
525
+ [EXCEPTIONS]
526
+
527
+ # Exceptions that will emit a warning when being caught. Defaults to
528
+ # "Exception"
529
+ overgeneral-exceptions=Exception
530
+
531
+
532
+ [TYPING]
533
+
534
+ # Set to ``no`` if the app / library does **NOT** need to support runtime
535
+ # introspection of type annotations. If you use type annotations
536
+ # **exclusively** for type checking of an application, you're probably fine.
537
+ # For libraries, evaluate if some users what to access the type hints at
538
+ # runtime first, e.g., through ``typing.get_type_hints``. Applies to Python
539
+ # versions 3.7 - 3.9
540
+ runtime-typing = no
541
+
542
+
543
+ [DEPRECATED_BUILTINS]
544
+
545
+ # List of builtins function names that should not be used, separated by a comma
546
+ bad-functions=map,input
547
+
548
+
549
+ [REFACTORING]
550
+
551
+ # Maximum number of nested blocks for function / method body
552
+ max-nested-blocks=5
553
+
554
+ # Complete name of functions that never returns. When checking for
555
+ # inconsistent-return-statements if a never returning function is called then
556
+ # it will be considered as an explicit return statement and no message will be
557
+ # printed.
558
+ never-returning-functions=sys.exit,argparse.parse_error
559
+
560
+
561
+ [STRING]
562
+
563
+ # This flag controls whether inconsistent-quotes generates a warning when the
564
+ # character used as a quote delimiter is used inconsistently within a module.
565
+ check-quote-consistency=no
566
+
567
+ # This flag controls whether the implicit-str-concat should generate a warning
568
+ # on implicit string concatenation in sequences defined over several lines.
569
+ check-str-concat-over-line-jumps=no
570
+
571
+
572
+ [CODE_STYLE]
573
+
574
+ # Max line length for which to sill emit suggestions. Used to prevent optional
575
+ # suggestions which would get split by a code formatter (e.g., black). Will
576
+ # default to the setting for ``max-line-length``.
577
+ #max-line-length-suggestions=
578
+
579
+ W0107:unnecessary-pass
.vscode/settings.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "python.languageServer": "Pylance",
3
+ "python.analysis.typeCheckingMode": "basic",
4
+ "python.analysis.diagnosticSeverityOverrides": {},
5
+ "python.analysis.typeshedPaths": [".venv/Lib/site-packages"]
6
+ }
Dockerfile ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use Python 3.10.9 as the base image for consistent runtime environment
2
+ FROM python:3.10.9
3
+
4
+ # Add metadata labels
5
+ LABEL maintainer="lamhieu.vk@gmail.com"
6
+ LABEL description="Lightweight embeddings service using FastAPI and Hugging Face Transformers"
7
+ LABEL version="1.0"
8
+
9
+ # Setup non-root user for security
10
+ RUN useradd -m -u 1000 user
11
+ USER user
12
+ ENV HOME=/home/user \
13
+ PATH=/home/user/.local/bin:$PATH
14
+
15
+ # Set working directory for all subsequent commands
16
+ WORKDIR $HOME/app
17
+
18
+ # Copy application files
19
+ # Copy requirements first to leverage Docker cache
20
+ COPY --chown=user requirements.txt .
21
+ COPY --chown=user . .
22
+
23
+ # Install Python dependencies
24
+ # --no-cache-dir reduces image size
25
+ # --upgrade ensures latest compatible versions
26
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
27
+
28
+ # Expose service port
29
+ EXPOSE 8000
30
+
31
+ # Launch FastAPI application using uvicorn server
32
+ # --host 0.0.0.0: Listen on all network interfaces
33
+ # --port 8000: Run on port 8000
34
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
README.md ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Lightweight Embeddings
3
+ emoji: 🌍
4
+ colorFrom: green
5
+ colorTo: green
6
+ sdk: docker
7
+ app_file: app.py
8
+ ---
9
+
10
+ # 🌍 LightweightEmbeddings: Multilingual, Fast, and Lightweight
11
+
12
+ **LightweightEmbeddings** is a high-performance framework designed for generating embeddings from **text** or **image-text inputs** across multiple languages. Engineered for efficiency and adaptability, it offers a perfect balance between speed and accuracy, making it ideal for **real-time applications** and **resource-constrained environments**.
13
+
14
+ ## ✨ Key Features
15
+
16
+ - **Multilingual Support**: Seamlessly process text in over 100+ languages for truly global applications.
17
+ - **Text and Image Embeddings**: Generate embeddings from text or image-text pairs using state-of-the-art models.
18
+ - **Optimized for Speed**: Built with lightweight transformer models and efficient backends to ensure rapid inference, even on low-resource systems.
19
+ - **Flexibility**: Supports multiple transformer models for diverse use cases:
20
+ - Text models: `multilingual-e5-small`, `paraphrase-multilingual-MiniLM-L12-v2`, `bge-m3`
21
+ - Image model: `google/siglip-base-patch16-256-multilingual`
22
+ - **Dockerized**: Deploy anywhere with ease using Docker, making it production-ready out of the box.
23
+ - **Interactive API**: Comes with a **Gradio-powered playground** and detailed REST API documentation.
24
+
25
+ ## 🚀 Use Cases
26
+
27
+ - **Search and Ranking**: Generate embeddings for advanced similarity-based ranking in search engines.
28
+ - **Recommendation Systems**: Use embeddings for personalized recommendations based on user input or preferences.
29
+ - **Multimodal Applications**: Combine text and image embeddings to power tasks like product catalog indexing, content moderation, or multimodal retrieval.
30
+ - **Language Understanding**: Enable semantic text analysis, summarization, or classification in multiple languages.
31
+
32
+ ## 🛠️ Getting Started
33
+
34
+ ### 1. Clone the Repository
35
+ ```bash
36
+ git clone https://github.com/lh0x00/lightweight-embeddings.git
37
+ cd lightweight-embeddings
38
+ ```
39
+
40
+ ### 2. Build and Run with Docker
41
+ Make sure Docker is installed and running on your machine.
42
+ ```bash
43
+ docker build -t lightweight-embeddings .
44
+ docker run -p 8000:8000 lightweight-embeddings
45
+ ```
46
+
47
+ The API will now be accessible at `http://localhost:8000`.
48
+
49
+ ## 📖 API Overview
50
+
51
+ ### Endpoints
52
+ - **`/v1/embeddings`**: Generate text or image embeddings using the model of your choice.
53
+ - **`/v1/rank`**: Rank candidate inputs based on similarity to a query.
54
+
55
+ ### Interactive Docs
56
+ - Visit the [Swagger UI](http://localhost:8000/docs) for detailed, interactive documentation.
57
+ - Explore additional resources with [ReDoc](http://localhost:8000/redoc).
58
+
59
+ ## 🔬 Playground
60
+
61
+ ### Embeddings Playground
62
+ - Test text and image embedding generation in the browser with a user-friendly **Gradio interface**.
63
+ - Simply visit `http://localhost:8000` after starting the server to access the playground.
64
+
65
+ ## 🌐 Resources
66
+
67
+ - **Documentation**: [Explore full documentation](https://lamhieu-lightweight-embeddings.hf.space/docs)
68
+ - **Hugging Face Space**: [Try the live demo](https://huggingface.co/spaces/lamhieu/lightweight-embeddings)
69
+ - **GitHub Repository**: [View source code](https://github.com/lh0x00/lightweight-embeddings)
70
+
71
+ ## 💡 Why LightweightEmbeddings?
72
+
73
+ 1. **Performance-Oriented**: Delivers rapid results without compromising on quality, ideal for real-world deployment.
74
+ 2. **Highly Adaptable**: Works in diverse environments, from cloud clusters to local devices.
75
+ 3. **Developer-Friendly**: Intuitive API design with robust documentation and an integrated playground for experimentation.
76
+
77
+ ## 👥 Contributors
78
+
79
+ - **lamhieu** – Creator and Maintainer ([GitHub](https://github.com/lh0x00))
80
+
81
+ Contributions are welcome! Check out the [contribution guidelines](https://github.com/lh0x00/lightweight-embeddings/blob/main/CONTRIBUTING.md).
82
+
83
+ ## 📜 License
84
+
85
+ This project is licensed under the **MIT License**. See the [LICENSE](https://github.com/lh0x00/lightweight-embeddings/blob/main/LICENSE) file for details.
app.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from lightweight_embeddings import app
lightweight_embeddings/__init__.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # filename: __init__.py
2
+
3
+ """
4
+ LightweightEmbeddings - FastAPI Application Entry Point
5
+
6
+ This application provides text and image embeddings using multiple text models and one image model.
7
+
8
+ Supported text model IDs:
9
+ - "multilingual-e5-small"
10
+ - "paraphrase-multilingual-MiniLM-L12-v2"
11
+ - "bge-m3"
12
+
13
+ Supported image model ID:
14
+ - "google/siglip-base-patch16-256-multilingual"
15
+ """
16
+
17
+ from fastapi import FastAPI
18
+ from fastapi.middleware.cors import CORSMiddleware
19
+ import gradio as gr
20
+ import requests
21
+ import json
22
+ from gradio.routes import mount_gradio_app
23
+
24
+ # Application metadata
25
+ __version__ = "1.0.0"
26
+ __author__ = "lamhieu"
27
+ __description__ = "Fast, lightweight, multilingual embeddings solution."
28
+
29
+ # Set your embeddings API URL here (change host/port if needed)
30
+ EMBEDDINGS_API_URL = "http://localhost:8000/v1/embeddings"
31
+
32
+ # Initialize FastAPI application
33
+ app = FastAPI(
34
+ title="Lightweight Embeddings API",
35
+ description=__description__,
36
+ version=__version__,
37
+ docs_url="/docs",
38
+ redoc_url="/redoc",
39
+ )
40
+
41
+ # Configure CORS
42
+ app.add_middleware(
43
+ CORSMiddleware,
44
+ allow_origins=["*"], # Adjust if needed for specific domains
45
+ allow_credentials=True,
46
+ allow_methods=["*"],
47
+ allow_headers=["*"],
48
+ )
49
+
50
+ # Include your existing router (which provides /v1/embeddings, /v1/rank, etc.)
51
+ from .router import router
52
+
53
+ app.include_router(router, prefix="/v1")
54
+
55
+
56
+ def call_embeddings_api(user_input: str, selected_model: str) -> str:
57
+ """
58
+ Send a request to the /v1/embeddings endpoint with the given model and input.
59
+ Return a pretty-printed JSON response or an error message.
60
+ """
61
+ payload = {
62
+ "model": selected_model,
63
+ "input": user_input,
64
+ }
65
+ headers = {"Content-Type": "application/json"}
66
+
67
+ try:
68
+ response = requests.post(
69
+ EMBEDDINGS_API_URL, json=payload, headers=headers, timeout=20
70
+ )
71
+ except requests.exceptions.RequestException as e:
72
+ return f"❌ Network Error: {str(e)}"
73
+
74
+ if response.status_code != 200:
75
+ # Provide detailed error message
76
+ return f"❌ API Error {response.status_code}: {response.text}"
77
+
78
+ try:
79
+ data = response.json()
80
+ return json.dumps(data, indent=2)
81
+ except ValueError:
82
+ return "❌ Failed to parse JSON from API response."
83
+
84
+
85
+ def create_main_interface():
86
+ """
87
+ Creates a Gradio Blocks interface showing project info and an embeddings playground.
88
+ """
89
+ # Metadata to be displayed
90
+ root_data = {
91
+ "project": "Lightweight Embeddings Service",
92
+ "version": "1.0.0",
93
+ "description": (
94
+ "Fast and efficient multilingual text and image embeddings service "
95
+ "powered by sentence-transformers, supporting 100+ languages and multi-modal inputs"
96
+ ),
97
+ "docs": "https://lamhieu-lightweight-embeddings.hf.space/docs",
98
+ "github": "https://github.com/lh0x00/lightweight-embeddings",
99
+ "spaces": "https://huggingface.co/spaces/lamhieu/lightweight-embeddings",
100
+ }
101
+
102
+ # Available model options for the dropdown
103
+ model_options = [
104
+ "multilingual-e5-small",
105
+ "paraphrase-multilingual-MiniLM-L12-v2",
106
+ "bge-m3",
107
+ "google/siglip-base-patch16-256-multilingual",
108
+ ]
109
+
110
+ with gr.Blocks(title="Lightweight Embeddings", theme="default") as demo:
111
+ # Project Info
112
+ gr.Markdown(
113
+ """
114
+ # 🎉 **Lightweight Embeddings Service** 🎉
115
+
116
+ Welcome to the **Lightweight Embeddings** API, a blazing-fast and flexible service
117
+ supporting **text** and **image** embeddings. Below you'll find key project details:
118
+ """
119
+ )
120
+ gr.Markdown(
121
+ f"""
122
+ **Project**: {root_data["project"]} 🚀
123
+ **Version**: {root_data["version"]}
124
+ **Description**: {root_data["description"]}
125
+
126
+ **Docs**: [Click here]({root_data["docs"]}) 😎
127
+ **GitHub**: [Check it out]({root_data["github"]}) 🐙
128
+ **Spaces**: [Explore]({root_data["spaces"]}) 🤗
129
+ """
130
+ )
131
+ gr.Markdown(
132
+ """
133
+ ---
134
+ ### 💡 How to Use
135
+ - Visit **/docs** or **/redoc** for interactive API documentation.
136
+ - Check out **/v1/embeddings** and **/v1/rank** endpoints for direct usage.
137
+ - Or try the simple playground below! Enjoy exploring a multilingual, multi-modal world! 🌏🌐
138
+ """
139
+ )
140
+
141
+ # Embeddings Playground
142
+ with gr.Accordion("🔬 Try the Embeddings Playground", open=True):
143
+ gr.Markdown(
144
+ "Enter your **text** or an **image URL**, pick a model, "
145
+ "then click **Generate** to get embeddings from the `/v1/embeddings` API."
146
+ )
147
+ input_text = gr.Textbox(
148
+ label="Input Text or Image URL",
149
+ placeholder="Type some text or paste an image URL...",
150
+ lines=3,
151
+ )
152
+ model_dropdown = gr.Dropdown(
153
+ choices=model_options,
154
+ value=model_options[0],
155
+ label="Select Model",
156
+ )
157
+ generate_btn = gr.Button("Generate Embeddings")
158
+ output_json = gr.Textbox(
159
+ label="Embeddings API Response",
160
+ lines=15,
161
+ interactive=False,
162
+ )
163
+
164
+ # Link the button to the inference function
165
+ generate_btn.click(
166
+ fn=call_embeddings_api,
167
+ inputs=[input_text, model_dropdown],
168
+ outputs=output_json,
169
+ )
170
+
171
+ return demo
172
+
173
+
174
+ # Create and mount the Gradio Blocks at the root path
175
+ main_interface = create_main_interface()
176
+ mount_gradio_app(app, main_interface, path="/")
177
+
178
+
179
+ # Startup and shutdown events
180
+ @app.on_event("startup")
181
+ async def startup_event():
182
+ """
183
+ Initialize resources (like model loading) when the application starts.
184
+ """
185
+ pass
186
+
187
+
188
+ @app.on_event("shutdown")
189
+ async def shutdown_event():
190
+ """
191
+ Perform cleanup before the application shuts down.
192
+ """
193
+ pass
lightweight_embeddings/router.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # filename: router.py
2
+
3
+ """
4
+ FastAPI Router for Embeddings Service
5
+
6
+ This file exposes the EmbeddingsService functionality via a RESTful API
7
+ to generate embeddings and rank candidates.
8
+
9
+ Supported Text Model IDs:
10
+ - "multilingual-e5-small"
11
+ - "paraphrase-multilingual-MiniLM-L12-v2"
12
+ - "bge-m3"
13
+
14
+ Supported Image Model ID:
15
+ - "google/siglip-base-patch16-256-multilingual"
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ from typing import List, Union
22
+ from enum import Enum
23
+
24
+ from fastapi import APIRouter, HTTPException
25
+ from pydantic import BaseModel, Field
26
+
27
+ from .service import ModelConfig, TextModelType, EmbeddingsService
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+ # Initialize FastAPI router
32
+ router = APIRouter(
33
+ tags=["v1"],
34
+ responses={404: {"description": "Not found"}},
35
+ )
36
+
37
+
38
+ class ModelType(str, Enum):
39
+ """
40
+ High-level distinction for text vs. image models.
41
+ """
42
+
43
+ TEXT = "text"
44
+ IMAGE = "image"
45
+
46
+
47
+ def detect_model_type(model_id: str) -> ModelType:
48
+ """
49
+ Detect whether the provided model ID is for text or image.
50
+
51
+ Supported text model IDs:
52
+ - "multilingual-e5-small"
53
+ - "paraphrase-multilingual-MiniLM-L12-v2"
54
+ - "bge-m3"
55
+
56
+ Supported image model ID:
57
+ - "google/siglip-base-patch16-256-multilingual"
58
+ (or any model containing "siglip" in its identifier).
59
+
60
+ Args:
61
+ model_id: String identifier of the model.
62
+
63
+ Returns:
64
+ ModelType.TEXT if it matches one of the recognized text model IDs,
65
+ ModelType.IMAGE if it matches (or contains "siglip").
66
+
67
+ Raises:
68
+ ValueError: If the model_id is not recognized as either text or image.
69
+ """
70
+ # Gather all known text model IDs (from TextModelType enum)
71
+ text_model_ids = {m.value for m in TextModelType}
72
+
73
+ # Simple check: if it's in text_model_ids, it's text;
74
+ # if 'siglip' is in the model ID, it's recognized as an image model.
75
+ if model_id in text_model_ids:
76
+ return ModelType.TEXT
77
+ elif "siglip" in model_id.lower():
78
+ return ModelType.IMAGE
79
+
80
+ error_msg = (
81
+ f"Unsupported model ID: '{model_id}'.\n"
82
+ "Valid text model IDs are: "
83
+ "'multilingual-e5-small', 'paraphrase-multilingual-MiniLM-L12-v2', 'bge-m3'.\n"
84
+ "Valid image model ID contains 'siglip', for example: 'google/siglip-base-patch16-256-multilingual'."
85
+ )
86
+ raise ValueError(error_msg)
87
+
88
+
89
+ # Pydantic Models for request/response
90
+ class EmbeddingRequest(BaseModel):
91
+ """
92
+ Request body for embedding creation.
93
+
94
+ Model IDs (text):
95
+ - "multilingual-e5-small"
96
+ - "paraphrase-multilingual-MiniLM-L12-v2"
97
+ - "bge-m3"
98
+
99
+ Model ID (image):
100
+ - "google/siglip-base-patch16-256-multilingual"
101
+ """
102
+
103
+ model: str = Field(
104
+ default=TextModelType.MULTILINGUAL_E5_SMALL.value,
105
+ description=(
106
+ "Model ID to use. Possible text models include: 'multilingual-e5-small', "
107
+ "'paraphrase-multilingual-MiniLM-L12-v2', 'bge-m3'. "
108
+ "For images, you can use: 'google/siglip-base-patch16-256-multilingual' "
109
+ "or any ID containing 'siglip'."
110
+ ),
111
+ )
112
+ input: Union[str, List[str]] = Field(
113
+ ...,
114
+ description=(
115
+ "Input text(s) or image path(s)/URL(s). "
116
+ "Accepts a single string or a list of strings."
117
+ ),
118
+ )
119
+
120
+
121
+ class RankRequest(BaseModel):
122
+ """
123
+ Request body for ranking candidates against queries.
124
+
125
+ Model IDs (text):
126
+ - "multilingual-e5-small"
127
+ - "paraphrase-multilingual-MiniLM-L12-v2"
128
+ - "bge-m3"
129
+
130
+ Model ID (image):
131
+ - "google/siglip-base-patch16-256-multilingual"
132
+ """
133
+
134
+ model: str = Field(
135
+ default=TextModelType.MULTILINGUAL_E5_SMALL.value,
136
+ description=(
137
+ "Model ID to use for the queries. Supported text models: "
138
+ "'multilingual-e5-small', 'paraphrase-multilingual-MiniLM-L12-v2', 'bge-m3'. "
139
+ "For image queries, use an ID containing 'siglip' such as 'google/siglip-base-patch16-256-multilingual'."
140
+ ),
141
+ )
142
+ queries: Union[str, List[str]] = Field(
143
+ ...,
144
+ description=(
145
+ "Query input(s): can be text(s) or image path(s)/URL(s). "
146
+ "If using an image model, ensure your inputs reference valid image paths or URLs."
147
+ ),
148
+ )
149
+ candidates: List[str] = Field(
150
+ ...,
151
+ description=(
152
+ "List of candidate texts to rank against the given queries. "
153
+ "Currently, all candidates must be text."
154
+ ),
155
+ )
156
+
157
+
158
+ class EmbeddingResponse(BaseModel):
159
+ """
160
+ Response structure for embedding creation.
161
+ """
162
+
163
+ object: str = "list"
164
+ data: List[dict]
165
+ model: str
166
+ usage: dict
167
+
168
+
169
+ class RankResponse(BaseModel):
170
+ """
171
+ Response structure for ranking results.
172
+ """
173
+
174
+ probabilities: List[List[float]]
175
+ cosine_similarities: List[List[float]]
176
+
177
+
178
+ # Initialize the service with default configuration
179
+ service_config = ModelConfig()
180
+ embeddings_service = EmbeddingsService(config=service_config)
181
+
182
+
183
+ @router.post("/embeddings", response_model=EmbeddingResponse, tags=["embeddings"])
184
+ async def create_embeddings(request: EmbeddingRequest):
185
+ """
186
+ Generate embeddings for the provided input text(s) or image(s).
187
+
188
+ Supported Model IDs for text:
189
+ - "multilingual-e5-small"
190
+ - "paraphrase-multilingual-MiniLM-L12-v2"
191
+ - "bge-m3"
192
+
193
+ Supported Model ID for image:
194
+ - "google/siglip-base-patch16-256-multilingual"
195
+
196
+ Steps:
197
+ 1. Detects model type (text or image) based on the model ID.
198
+ 2. Adjusts the service configuration accordingly.
199
+ 3. Produces embeddings via the EmbeddingsService.
200
+ 4. Returns embedding vectors along with usage information.
201
+
202
+ Raises:
203
+ HTTPException: For any errors during model detection or embedding generation.
204
+ """
205
+ try:
206
+ modality = detect_model_type(request.model)
207
+
208
+ # Adjust global config based on the detected modality
209
+ if modality == ModelType.TEXT:
210
+ service_config.text_model_type = TextModelType(request.model)
211
+ else:
212
+ service_config.image_model_id = request.model
213
+
214
+ # Generate embeddings asynchronously
215
+ embeddings = await embeddings_service.generate_embeddings(
216
+ input_data=request.input, modality=modality.value
217
+ )
218
+
219
+ # Estimate tokens only if it's text
220
+ total_tokens = 0
221
+ if modality == ModelType.TEXT:
222
+ total_tokens = embeddings_service.estimate_tokens(request.input)
223
+
224
+ return {
225
+ "object": "list",
226
+ "data": [
227
+ {
228
+ "object": "embedding",
229
+ "index": idx,
230
+ "embedding": emb.tolist(),
231
+ }
232
+ for idx, emb in enumerate(embeddings)
233
+ ],
234
+ "model": request.model,
235
+ "usage": {
236
+ "prompt_tokens": total_tokens,
237
+ "total_tokens": total_tokens,
238
+ },
239
+ }
240
+
241
+ except Exception as e:
242
+ error_msg = (
243
+ "Failed to generate embeddings. Please verify your model ID, input data, and server logs.\n"
244
+ f"Error Details: {str(e)}"
245
+ )
246
+ logger.error(error_msg)
247
+ raise HTTPException(status_code=500, detail=error_msg)
248
+
249
+
250
+ @router.post("/rank", response_model=RankResponse, tags=["rank"])
251
+ async def rank_candidates(request: RankRequest):
252
+ """
253
+ Rank the given candidate texts against the provided queries.
254
+
255
+ Supported Model IDs for text queries:
256
+ - "multilingual-e5-small"
257
+ - "paraphrase-multilingual-MiniLM-L12-v2"
258
+ - "bge-m3"
259
+
260
+ Supported Model ID for image queries:
261
+ - "google/siglip-base-patch16-256-multilingual"
262
+
263
+ Steps:
264
+ 1. Detects model type (text or image) based on the query model ID.
265
+ 2. Adjusts the service configuration accordingly.
266
+ 3. Generates embeddings for the queries (text or image).
267
+ 4. Generates embeddings for the candidates (always text).
268
+ 5. Computes cosine similarities and returns softmax-normalized probabilities.
269
+
270
+ Raises:
271
+ HTTPException: For any errors during model detection or ranking.
272
+ """
273
+ try:
274
+ modality = detect_model_type(request.model)
275
+
276
+ # Adjust global config based on the detected modality
277
+ if modality == ModelType.TEXT:
278
+ service_config.text_model_type = TextModelType(request.model)
279
+ else:
280
+ service_config.image_model_id = request.model
281
+
282
+ # Perform the ranking
283
+ results = await embeddings_service.rank(
284
+ queries=request.queries,
285
+ candidates=request.candidates,
286
+ modality=modality.value,
287
+ )
288
+ return results
289
+
290
+ except Exception as e:
291
+ error_msg = (
292
+ "Failed to rank candidates. Please verify your model ID, input data, and server logs.\n"
293
+ f"Error Details: {str(e)}"
294
+ )
295
+ logger.error(error_msg)
296
+ raise HTTPException(status_code=500, detail=error_msg)
lightweight_embeddings/service.py ADDED
@@ -0,0 +1,477 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # filename: service.py
2
+
3
+ """
4
+ Lightweight Embeddings Service Module
5
+
6
+ This module provides a service for generating and comparing embeddings from text and images
7
+ using state-of-the-art transformer models. It supports both CPU and GPU inference.
8
+
9
+ Key Features:
10
+ - Text and image embedding generation
11
+ - Cross-modal similarity ranking
12
+ - Batch processing support
13
+ - Asynchronous API support
14
+
15
+ Supported Text Model IDs:
16
+ - "multilingual-e5-small"
17
+ - "paraphrase-multilingual-MiniLM-L12-v2"
18
+ - "bge-m3"
19
+
20
+ Supported Image Model ID (default):
21
+ - "google/siglip-base-patch16-256-multilingual"
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import logging
27
+ from enum import Enum
28
+ from typing import List, Union, Literal, Dict, Optional, NamedTuple
29
+ from dataclasses import dataclass
30
+ from pathlib import Path
31
+ from io import BytesIO
32
+
33
+ import requests
34
+ import numpy as np
35
+ import torch
36
+ from PIL import Image
37
+ from sentence_transformers import SentenceTransformer
38
+ from transformers import AutoProcessor, AutoModel
39
+
40
+ # Configure logging
41
+ logger = logging.getLogger(__name__)
42
+ logging.basicConfig(level=logging.INFO)
43
+
44
+ # Default Model IDs
45
+ TEXT_MODEL_ID = "Xenova/multilingual-e5-small"
46
+ IMAGE_MODEL_ID = "google/siglip-base-patch16-256-multilingual"
47
+
48
+
49
+ class TextModelType(str, Enum):
50
+ """
51
+ Enumeration of supported text models.
52
+ Please ensure the ONNX files and Hugging Face model IDs are consistent
53
+ with your local or remote environment.
54
+ """
55
+
56
+ MULTILINGUAL_E5_SMALL = "multilingual-e5-small"
57
+ PARAPHRASE_MULTILINGUAL_MINILM_L12_V2 = "paraphrase-multilingual-MiniLM-L12-v2"
58
+ BGE_M3 = "bge-m3"
59
+
60
+
61
+ class ModelInfo(NamedTuple):
62
+ """
63
+ Simple container for mapping a given text model type
64
+ to its Hugging Face model repository and the local ONNX file path.
65
+ """
66
+
67
+ model_id: str
68
+ onnx_file: str
69
+
70
+
71
+ @dataclass
72
+ class ModelConfig:
73
+ """
74
+ Configuration settings for model providers, backends, and defaults.
75
+ """
76
+
77
+ provider: str = "CPUExecutionProvider"
78
+ backend: str = "onnx"
79
+ logit_scale: float = 4.60517
80
+ text_model_type: TextModelType = TextModelType.MULTILINGUAL_E5_SMALL
81
+ image_model_id: str = IMAGE_MODEL_ID
82
+
83
+ @property
84
+ def text_model_info(self) -> ModelInfo:
85
+ """
86
+ Retrieves the ModelInfo for the currently selected text_model_type.
87
+ """
88
+ model_configs = {
89
+ TextModelType.MULTILINGUAL_E5_SMALL: ModelInfo(
90
+ "Xenova/multilingual-e5-small",
91
+ "onnx/model_quantized.onnx",
92
+ ),
93
+ TextModelType.PARAPHRASE_MULTILINGUAL_MINILM_L12_V2: ModelInfo(
94
+ "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
95
+ "onnx/model_quint8_avx2.onnx",
96
+ ),
97
+ TextModelType.BGE_M3: ModelInfo(
98
+ "BAAI/bge-m3",
99
+ "model.onnx",
100
+ ),
101
+ }
102
+ return model_configs[self.text_model_type]
103
+
104
+
105
+ class EmbeddingsService:
106
+ """
107
+ Service for generating and comparing text/image embeddings.
108
+
109
+ This service supports multiple text models and a single image model.
110
+ It provides methods for:
111
+ - Generating text embeddings
112
+ - Generating image embeddings
113
+ - Ranking candidates by similarity
114
+ """
115
+
116
+ def __init__(self, config: Optional[ModelConfig] = None) -> None:
117
+ """
118
+ Initialize the EmbeddingsService.
119
+
120
+ Args:
121
+ config: Optional ModelConfig object to override default settings.
122
+ """
123
+ # Determine whether GPU (CUDA) is available
124
+ self.device = "cuda" if torch.cuda.is_available() else "cpu"
125
+
126
+ # Use the provided config or fall back to defaults
127
+ self.config = config or ModelConfig()
128
+
129
+ # Dictionary to hold multiple text models
130
+ self.text_models: Dict[TextModelType, SentenceTransformer] = {}
131
+
132
+ # Load all models (text + image) into memory
133
+ self._load_models()
134
+
135
+ def _load_models(self) -> None:
136
+ """
137
+ Load text and image models into memory.
138
+
139
+ This pre-loads all text models defined in the TextModelType enum
140
+ and a single image model, enabling quick switching at runtime.
141
+ """
142
+ try:
143
+ # Load all text models
144
+ for model_type in TextModelType:
145
+ model_info = ModelConfig(text_model_type=model_type).text_model_info
146
+ logger.info(f"Loading text model: {model_info.model_id}")
147
+
148
+ self.text_models[model_type] = SentenceTransformer(
149
+ model_info.model_id,
150
+ device=self.device,
151
+ backend=self.config.backend,
152
+ model_kwargs={
153
+ "provider": self.config.provider,
154
+ "file_name": model_info.onnx_file,
155
+ },
156
+ )
157
+
158
+ logger.info(f"Loading image model: {self.config.image_model_id}")
159
+ self.image_model = AutoModel.from_pretrained(self.config.image_model_id).to(
160
+ self.device
161
+ )
162
+ self.image_processor = AutoProcessor.from_pretrained(
163
+ self.config.image_model_id
164
+ )
165
+
166
+ logger.info(f"All models loaded successfully on {self.device}.")
167
+
168
+ except Exception as e:
169
+ logger.error(
170
+ "Model loading failed. Please ensure you have valid model IDs and local files.\n"
171
+ f"Error details: {str(e)}"
172
+ )
173
+ raise RuntimeError(f"Failed to load models: {str(e)}") from e
174
+
175
+ @staticmethod
176
+ def _validate_text_input(input_text: Union[str, List[str]]) -> List[str]:
177
+ """
178
+ Validate and standardize the input for text embeddings.
179
+
180
+ Args:
181
+ input_text: Either a single string or a list of strings.
182
+
183
+ Returns:
184
+ A list of strings to process.
185
+
186
+ Raises:
187
+ ValueError: If input_text is empty or not string-based.
188
+ """
189
+ if isinstance(input_text, str):
190
+ return [input_text]
191
+ if not isinstance(input_text, list) or not all(
192
+ isinstance(x, str) for x in input_text
193
+ ):
194
+ raise ValueError(
195
+ "Text input must be a single string or a list of strings. "
196
+ "Found a different data type instead."
197
+ )
198
+ if not input_text:
199
+ raise ValueError("Text input list cannot be empty.")
200
+ return input_text
201
+
202
+ @staticmethod
203
+ def _validate_modality(modality: str) -> None:
204
+ """
205
+ Validate the input modality.
206
+
207
+ Args:
208
+ modality: Must be either 'text' or 'image'.
209
+
210
+ Raises:
211
+ ValueError: If modality is neither 'text' nor 'image'.
212
+ """
213
+ if modality not in ["text", "image"]:
214
+ raise ValueError(
215
+ "Invalid modality. Please specify 'text' or 'image' for embeddings."
216
+ )
217
+
218
+ def _process_image(self, image_path: Union[str, Path]) -> torch.Tensor:
219
+ """
220
+ Load and preprocess an image from either a local path or a URL.
221
+
222
+ Args:
223
+ image_path: Path to the local image file or a URL.
224
+
225
+ Returns:
226
+ Torch Tensor suitable for model input.
227
+
228
+ Raises:
229
+ ValueError: If the image file or URL cannot be loaded.
230
+ """
231
+ try:
232
+ if str(image_path).startswith("http"):
233
+ response = requests.get(image_path, timeout=10)
234
+ response.raise_for_status()
235
+ image_content = BytesIO(response.content)
236
+ else:
237
+ image_content = image_path
238
+
239
+ image = Image.open(image_content).convert("RGB")
240
+ processed = self.image_processor(images=image, return_tensors="pt").to(
241
+ self.device
242
+ )
243
+ return processed
244
+
245
+ except Exception as e:
246
+ raise ValueError(
247
+ f"Failed to process image at '{image_path}'. Check the path/URL and file format.\n"
248
+ f"Details: {str(e)}"
249
+ ) from e
250
+
251
+ def _generate_text_embeddings(self, texts: List[str]) -> np.ndarray:
252
+ """
253
+ Helper method to generate text embeddings for a list of texts
254
+ using the currently configured text model.
255
+
256
+ Args:
257
+ texts: A list of text strings.
258
+
259
+ Returns:
260
+ Numpy array of shape (num_texts, embedding_dim).
261
+
262
+ Raises:
263
+ RuntimeError: If the text model fails to generate embeddings.
264
+ """
265
+ try:
266
+ logger.info(
267
+ f"Generating embeddings for {len(texts)} text items using model: "
268
+ f"{self.config.text_model_type}"
269
+ )
270
+ # Select the preloaded text model based on the current config
271
+ model = self.text_models[self.config.text_model_type]
272
+ embeddings = model.encode(texts)
273
+ return embeddings
274
+ except Exception as e:
275
+ error_msg = (
276
+ f"Error generating text embeddings with model: {self.config.text_model_type}. "
277
+ f"Details: {str(e)}"
278
+ )
279
+ logger.error(error_msg)
280
+ raise RuntimeError(error_msg) from e
281
+
282
+ def _generate_image_embeddings(
283
+ self, input_data: Union[str, List[str]], batch_size: Optional[int]
284
+ ) -> np.ndarray:
285
+ """
286
+ Helper method to generate image embeddings.
287
+
288
+ Args:
289
+ input_data: Either a single image path/URL or a list of them.
290
+ batch_size: Batch size for processing images in chunks.
291
+ If None, process all at once.
292
+
293
+ Returns:
294
+ Numpy array of shape (num_images, embedding_dim).
295
+
296
+ Raises:
297
+ RuntimeError: If the image model fails to generate embeddings.
298
+ """
299
+ try:
300
+ if isinstance(input_data, str):
301
+ # Single image scenario
302
+ processed = self._process_image(input_data)
303
+ with torch.no_grad():
304
+ embedding = self.image_model.get_image_features(**processed)
305
+ return embedding.cpu().numpy()
306
+
307
+ # Multiple images scenario
308
+ logger.info(f"Generating embeddings for {len(input_data)} images.")
309
+ if batch_size is None:
310
+ # Process all images at once
311
+ processed_batches = [
312
+ self._process_image(img_path) for img_path in input_data
313
+ ]
314
+ with torch.no_grad():
315
+ # Concatenate all images along the batch dimension
316
+ batch_keys = processed_batches[0].keys()
317
+ concatenated = {
318
+ k: torch.cat([pb[k] for pb in processed_batches], dim=0)
319
+ for k in batch_keys
320
+ }
321
+ embedding = self.image_model.get_image_features(**concatenated)
322
+ return embedding.cpu().numpy()
323
+
324
+ # Process images in smaller batches
325
+ embeddings_list = []
326
+ for i, img_path in enumerate(input_data):
327
+ if i % batch_size == 0:
328
+ logger.debug(
329
+ f"Processing image batch {i // batch_size + 1} with size up to {batch_size}."
330
+ )
331
+ processed = self._process_image(img_path)
332
+ with torch.no_grad():
333
+ embedding = self.image_model.get_image_features(**processed)
334
+ embeddings_list.append(embedding.cpu().numpy())
335
+
336
+ return np.vstack(embeddings_list)
337
+
338
+ except Exception as e:
339
+ error_msg = (
340
+ f"Error generating image embeddings with model: {self.config.image_model_id}. "
341
+ f"Details: {str(e)}"
342
+ )
343
+ logger.error(error_msg)
344
+ raise RuntimeError(error_msg) from e
345
+
346
+ async def generate_embeddings(
347
+ self,
348
+ input_data: Union[str, List[str]],
349
+ modality: Literal["text", "image"] = "text",
350
+ batch_size: Optional[int] = None,
351
+ ) -> np.ndarray:
352
+ """
353
+ Asynchronously generate embeddings for text or image inputs.
354
+
355
+ Args:
356
+ input_data: A string or list of strings (text/image paths/URLs).
357
+ modality: "text" for text data or "image" for image data.
358
+ batch_size: Optional batch size for processing images in chunks.
359
+
360
+ Returns:
361
+ Numpy array of embeddings.
362
+
363
+ Raises:
364
+ ValueError: If the modality is invalid.
365
+ """
366
+ self._validate_modality(modality)
367
+
368
+ if modality == "text":
369
+ texts = self._validate_text_input(input_data)
370
+ return self._generate_text_embeddings(texts)
371
+ else:
372
+ return self._generate_image_embeddings(input_data, batch_size)
373
+
374
+ async def rank(
375
+ self,
376
+ queries: Union[str, List[str]],
377
+ candidates: List[str],
378
+ modality: Literal["text", "image"] = "text",
379
+ batch_size: Optional[int] = None,
380
+ ) -> Dict[str, List[List[float]]]:
381
+ """
382
+ Rank a set of candidate texts against one or more queries using cosine similarity
383
+ and a softmax to produce probability-like scores.
384
+
385
+ Args:
386
+ queries: Query text(s) or image path(s)/URL(s).
387
+ candidates: Candidate texts to be ranked.
388
+ (Note: This implementation always treats candidates as text.)
389
+ modality: "text" for text queries or "image" for image queries.
390
+ batch_size: Batch size if images are processed in chunks.
391
+
392
+ Returns:
393
+ Dictionary containing:
394
+ - "probabilities": 2D list of softmax-normalized scores.
395
+ - "cosine_similarities": 2D list of raw cosine similarity values.
396
+
397
+ Raises:
398
+ RuntimeError: If the query or candidate embeddings fail to generate.
399
+ """
400
+ logger.info(
401
+ f"Ranking {len(candidates)} candidates against "
402
+ f"{len(queries) if isinstance(queries, list) else 1} query item(s)."
403
+ )
404
+
405
+ # Generate embeddings for queries
406
+ query_embeds = await self.generate_embeddings(
407
+ queries, modality=modality, batch_size=batch_size
408
+ )
409
+
410
+ # Generate embeddings for candidates (always text)
411
+ candidate_embeds = await self.generate_embeddings(
412
+ candidates, modality="text", batch_size=batch_size
413
+ )
414
+
415
+ # Compute cosine similarity and scaled probabilities
416
+ cosine_sims = self.cosine_similarity(query_embeds, candidate_embeds)
417
+ logit_scale = np.exp(self.config.logit_scale)
418
+ probabilities = self.softmax(logit_scale * cosine_sims)
419
+
420
+ return {
421
+ "probabilities": probabilities.tolist(),
422
+ "cosine_similarities": cosine_sims.tolist(),
423
+ }
424
+
425
+ def estimate_tokens(self, input_data: Union[str, List[str]]) -> int:
426
+ """
427
+ Roughly estimate the total number of tokens in the given text(s).
428
+
429
+ Args:
430
+ input_data: A string or list of strings representing text input.
431
+
432
+ Returns:
433
+ Estimated token count (int).
434
+
435
+ Raises:
436
+ ValueError: If the input is not valid text data.
437
+ """
438
+ texts = self._validate_text_input(input_data)
439
+ # Very rough approximation: assume ~4 characters per token
440
+ total_chars = sum(len(t) for t in texts)
441
+ return max(1, round(total_chars / 4))
442
+
443
+ @staticmethod
444
+ def softmax(scores: np.ndarray) -> np.ndarray:
445
+ """
446
+ Apply softmax along the last dimension of the scores array.
447
+
448
+ Args:
449
+ scores: Numpy array of shape (..., num_candidates).
450
+
451
+ Returns:
452
+ Numpy array of softmax-normalized values, same shape as scores.
453
+ """
454
+ exp_scores = np.exp(scores - np.max(scores, axis=-1, keepdims=True))
455
+ return exp_scores / np.sum(exp_scores, axis=-1, keepdims=True)
456
+
457
+ @staticmethod
458
+ def cosine_similarity(
459
+ query_embeds: np.ndarray, candidate_embeds: np.ndarray
460
+ ) -> np.ndarray:
461
+ """
462
+ Compute the cosine similarity between two sets of vectors.
463
+
464
+ Args:
465
+ query_embeds: Numpy array of shape (num_queries, embed_dim).
466
+ candidate_embeds: Numpy array of shape (num_candidates, embed_dim).
467
+
468
+ Returns:
469
+ 2D Numpy array of shape (num_queries, num_candidates)
470
+ containing cosine similarity scores.
471
+ """
472
+ # Normalize embeddings
473
+ query_norm = query_embeds / np.linalg.norm(query_embeds, axis=1, keepdims=True)
474
+ candidate_norm = candidate_embeds / np.linalg.norm(
475
+ candidate_embeds, axis=1, keepdims=True
476
+ )
477
+ return np.dot(query_norm, candidate_norm.T)
pyproject.toml ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [tool.poetry]
2
+ name = "lightweight-embeddings"
3
+ version = "0.1.0"
4
+ description = "Fast, lightweight, multilingual embeddings solution."
5
+ authors = ["Hieu Lam <lamhieu.vk@gmail.com>"]
6
+ readme = "README.md"
7
+
8
+ [tool.poetry.dependencies]
9
+ python = "^3.10"
10
+
11
+
12
+ [build-system]
13
+ requires = ["poetry-core"]
14
+ build-backend = "poetry.core.masonry.api"
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ gradio
2
+ fastapi
3
+ uvicorn
4
+ requests
5
+ pydantic
6
+ sentence-transformers[onnx]==3.3.1
7
+ sentencepiece==0.2.0
8
+ torch==2.4.0
9
+ transformers==4.45.0