Visão geral do projeto
Visão geral do projeto
URL do projeto: https://github.com/milvus-io/milvus_cli
Preparação: Python3.8
, Click 8.0.x
Agrupar comandos
Criar um comando
import click
from utils import PyOrm
@click.group(no_args_is_help=False, add_help_option=False, invoke_without_command=True)
@click.pass_context
def cli(ctx):
"""Milvus CLI"""
ctx.obj = PyOrm() # PyOrm is a util class which wraps the milvus python SDK. You can pass any class instance here. Any command function passed by @click.obj can call it.
if __name__ == '__main__':
cli()
Conforme o código acima, usamos @click.group()
para criar um grupo de comandos cli
como ponto de entrada. Para implementar um prompt CLI, precisamos de desativar as mensagens de ajuda para a entrada, por isso adicionamos no_args_is_help=False
, add_help_option=False
e invoke_without_command=True
. E nada será impresso se introduzirmos cli
apenas no terminal.
Além disso, utilizamos @click.pass_context
para passar um contexto a este grupo para utilização posterior.
Criar um subcomando do grupo de comandos
De seguida, adicionamos o primeiro subcomando help
em cli
:
# Print the help message of specified command.
def print_help_msg(command):
with click.Context(command) as ctx:
click.echo(command.get_help(ctx))
# Use @cli.command() to create a sub command of cli.
@cli.command()
def help():
"""Show help messages."""
# Print help message of cli.
click.echo(print_help_msg(cli))
Agora podemos utilizar cli help
no terminal:
$ python milvus_cli/scripts/milvus_cli.py help
Criar um subgrupo de um grupo de comandos
Não só queremos ter um subcomando como cli help
, mas também precisamos de um subgrupo de comandos como cli list collection
, cli list partition
e cli list indexes
.
Em primeiro lugar, criamos um subgrupo de comandos list
. Neste caso, podemos passar o primeiro parâmetro para @cli.group
como nome do comando em vez de utilizar o nome da função predefinida, de modo a reduzir a duplicação de nomes de funções.
Atenção, aqui utilizamos @cli.group()
em vez de @click.group
para criar um subgrupo do grupo de origem.
Utilizamos @click.pass_obj
para passar context.obj
para os subcomandos deste subgrupo.
@cli.group('list', no_args_is_help=False)
@click.pass_obj
def listDetails(obj):
"""List collections, partitions and indexes."""
pass
Depois, adicionamos alguns subcomandos a este subgrupo através de @listDetails.command()
(e não @cli.command()
). Este é apenas um exemplo, pode ignorar a implementação e discuti-la-emos mais tarde.
@listDetails.command()
@click.option('--timeout', 'timeout', help="[Optional] - An optional duration of time in seconds to allow for the RPC. When timeout is set to None, client waits until server response or error occur.", default=None)
@click.option('--show-loaded', 'showLoaded', help="[Optional] - Only show loaded collections.", default=False)
@click.pass_obj
def collections(obj, timeout, showLoaded):
"""List all collections."""
try:
obj.checkConnection()
click.echo(obj.listCollections(timeout, showLoaded))
except Exception as e:
click.echo(message=e, err=True)
@listDetails.command()
@click.option('-c', '--collection', 'collection', help='The name of collection.', default='')
@click.pass_obj
def partitions(obj, collection):
"""List all partitions of the specified collection."""
try:
obj.checkConnection()
validateParamsByCustomFunc(
obj.getTargetCollection, 'Collection Name Error!', collection)
click.echo(obj.listPartitions(collection))
except Exception as e:
click.echo(message=e, err=True)
Depois de tudo isto concluído, temos um miltigroup de comandos com o seguinte aspeto
imagem
Personalizar um comando
Adicionar opções
É possível adicionar algumas opções a um comando que será utilizado como cli --test-option value
.
Eis um exemplo: adicionamos três opções alias
, host
e port
para especificar um endereço para ligação ao Milvus.
Os dois primeiros parâmetros definem o nome curto e completo da opção, o terceiro parâmetro define o nome da variável, o parâmetro help
especifica a mensagem curta de ajuda, o parâmetro default
especifica o valor predefinido e o parâmetro type
especifica o tipo de valor.
E todos os valores das opções serão passados para a função por ordem de definição.
@cli.command(no_args_is_help=False)
@click.option('-a', '--alias', 'alias', help="Milvus link alias name, default is `default`.", default='default', type=str)
@click.option('-h', '--host', 'host', help="Host name, default is `127.0.0.1`.", default='127.0.0.1', type=str)
@click.option('-p', '--port', 'port', help="Port, default is `19530`.", default=19530, type=int)
@click.pass_obj
def connect(obj, alias, host, port):
pass
Adicionar opções de sinalização
Utilizámos as opções acima para passar um valor, mas, por vezes, só precisamos de um sinalizador como um valor booleano.
Como no exemplo abaixo, a opção autoId
é uma opção de sinalização e não passa quaisquer dados para a função, pelo que podemos utilizá-la como cli create collection -c c_name -p p_name -a
.
@createDetails.command('collection')
@click.option('-c', '--collection-name', 'collectionName', help='Collection name to be created.', default='')
@click.option('-p', '--schema-primary-field', 'primaryField', help='Primary field name.', default='')
@click.option('-a', '--schema-auto-id', 'autoId', help='Enable auto id.', default=False, is_flag=True)
@click.pass_obj
def createCollection(obj, collectionName, primaryField, autoId, description, fields):
pass
Adicionar argumentos
Neste projeto, substituímos a utilização de todos os argumentos pela utilização de opções. Mas ainda introduzimos o uso de argumentos aqui. Diferente das opções, os argumentos são usados como cli COMMAND [OPTIONS] ARGUEMENTS
. Se convertermos o exemplo acima na utilização de argumentos, será assim:
@createDetails.command('collection')
@click.argument('collectionName')
@click.option('-p', '--schema-primary-field', 'primaryField', help='Primary field name.', default='')
@click.option('-a', '--schema-auto-id', 'autoId', help='Enable auto id.', default=False, is_flag=True)
@click.pass_obj
def createCollection(obj, collectionName, primaryField, autoId, description, fields):
pass
Então a utilização deve ser cli create collection c_name -p p_name -a
.
Adicionar mensagem de ajuda completa
Tal como definimos a mensagem de ajuda curta acima, podemos definir a mensagem de ajuda completa na função:
@cli.command(no_args_is_help=False)
@click.option('-a', '--alias', 'alias', help="Milvus link alias name, default is `default`.", default='default', type=str)
@click.option('-h', '--host', 'host', help="Host name, default is `127.0.0.1`.", default='127.0.0.1', type=str)
@click.option('-p', '--port', 'port', help="Port, default is `19530`.", default=19530, type=int)
@click.pass_obj
def connect(obj, alias, host, port):
"""
Connect to Milvus.
Example:
milvus_cli > connect -h 127.0.0.1 -p 19530 -a default
"""
try:
obj.connect(alias, host, port)
except Exception as e:
click.echo(message=e, err=True)
else:
click.echo("Connect Milvus successfully!")
click.echo(obj.showConnection(alias))
O primeiro bloco dentro da função é a mensagem de ajuda que será impressa depois de introduzirmos cli connect --help
.
milvus_cli > connect --help
Usage: milvus_cli.py connect [OPTIONS]
Connect to Milvus.
Example:
milvus_cli > connect -h 127.0.0.1 -p 19530 -a default
Options:
-a, --alias TEXT Milvus link alias name, default is `default`.
-h, --host TEXT Host name, default is `127.0.0.1`.
-p, --port INTEGER Port, default is `19530`.
--help Show this message and exit.
Adicionar confirmação
Por vezes, precisamos que o utilizador confirme uma ação, especialmente a eliminação de algo. Podemos adicionar click.confirm
para fazer uma pausa e pedir ao utilizador para confirmar:
@deleteSth.command('collection')
@click.option('-c', '--collection', 'collectionName', help='The name of collection to be deleted.', default='')
@click.option('-t', '--timeout', 'timeout', help='An optional duration of time in seconds to allow for the RPC. If timeout is set to None, the client keeps waiting until the server responds or an error occurs.', default=None, type=int)
@click.pass_obj
def deleteCollection(obj, collectionName, timeout):
"""
Drops the collection together with its index files.
Example:
milvus_cli > delete collection -c car
"""
click.echo(
"Warning!\nYou are trying to delete the collection with data. This action cannot be undone!\n")
if not click.confirm('Do you want to continue?'):
return
pass
Como no exemplo acima, uma conversa de confirmação será apresentada como Aborted!ant to continue? [y/N]:
.
Adicionar prompts
Para implementar avisos, só precisamos de adicionar click.prompt
.
@cli.command()
@click.pass_obj
def query(obj):
"""
Query with a set of criteria, and results in a list of records that match the query exactly.
"""
collectionName = click.prompt(
'Collection name', type=click.Choice(obj._list_collection_names()))
expr = click.prompt('The query expression(field_name in [x,y])')
partitionNames = click.prompt(
f'The names of partitions to search(split by "," if multiple) {obj._list_partition_names(collectionName)}', default='')
outputFields = click.prompt(
f'Fields to return(split by "," if multiple) {obj._list_field_names(collectionName)}', default='')
timeout = click.prompt('timeout', default='')
pass
O aviso será apresentado quando cada click.prompt
. Utilizamos alguns prompts em série para que pareça uma conversa contínua. Isto garante que o utilizador introduzirá os dados pela ordem pretendida. Neste caso, é necessário que o utilizador escolha primeiro uma coleção e que obtenha todas as partições desta coleção e, em seguida, mostre-as ao utilizador para que este as escolha.
Adicionar escolhas
Por vezes, se o utilizador quiser introduzir apenas um intervalo/tipo de valor limitado, pode adicionar type=click.Choice([<any>])
a click.prompt
, click.options
e etc...
Por exemplo,
collectionName = click.prompt(
'Collection name', type=click.Choice(['collection_1', 'collection_2']))
Assim, o utilizador só pode introduzir collection_1
ou collection_2
. Se introduzir outros valores, surgirá um erro.
Adicionar ecrã limpo
Pode utilizar click.clear()
para o implementar.
@cli.command()
def clear():
"""Clear screen."""
click.clear()
Sugestões adicionais
- O valor predefinido é
None
, pelo que não faz sentido se especificar o valor predefinido comoNone
. E o valor predefinidoNone
fará com queclick.prompt
apareça continuamente se quiser deixar um valor vazio para o ultrapassar.
Implementar um prompt CLI para o utilizador introduzir dados
Porquê um prompt CLI
Para o funcionamento da base de dados, precisamos de uma ligação contínua a uma instância. Se utilizarmos o modo de linha de comandos de origem, a ligação será interrompida após cada comando executado. Também queremos armazenar alguns dados quando usamos o CLI, e limpá-los depois de sair.
Implementar
- Use
while True
para ouvir continuamente a entrada do utilizador.
def runCliPrompt():
while True:
astr = input('milvus_cli > ')
try:
cli(astr.split())
except SystemExit:
# trap argparse error message
# print('error', SystemExit)
continue
if __name__ == '__main__':
runCliPrompt()
- Usar apenas
input
fará com que as teclas de setaup
,down
,left
,right
, a teclatab
e algumas outras teclas sejam convertidas em string Acsii automaticamente. Além disso, os comandos do histórico não podem ser lidos da sessão. Portanto, adicionamosreadline
à funçãorunCliPrompt
.
def runCliPrompt():
while True:
import readline
readline.set_completer_delims(' \t\n;')
astr = input('milvus_cli > ')
try:
cli(astr.split())
except SystemExit:
# trap argparse error message
# print('error', SystemExit)
continue
- Adicionar
quit
CLI.
@cli.command('exit')
def quitapp():
"""Exit the CLI."""
global quitapp
quitapp = True
quitapp = False # global flag
def runCliPrompt():
while not quitapp:
import readline
readline.set_completer_delims(' \t\n;')
astr = input('milvus_cli > ')
try:
cli(astr.split())
except SystemExit:
# trap argparse error message
# print('error', SystemExit)
continue
- Pegar o erro
KeyboardInterrupt
quando usarctrl C
para sair.
def runCliPrompt():
try:
while not quitapp:
import readline
readline.set_completer_delims(' \t\n;')
astr = input('milvus_cli > ')
try:
cli(astr.split())
except SystemExit:
# trap argparse error message
# print('error', SystemExit)
continue
except KeyboardInterrupt:
sys.exit(0)
- Depois de tudo resolvido, a CLI agora se parece com:
milvus_cli >
milvus_cli > connect
+-------+-----------+
| Host | 127.0.0.1 |
| Port | 19530 |
| Alias | default |
+-------+-----------+
milvus_cli > help
Usage: [OPTIONS] COMMAND [ARGS]...
Milvus CLI
Commands:
clear Clear screen.
connect Connect to Milvus.
create Create collection, partition and index.
delete Delete specified collection, partition and index.
describe Describe collection or partition.
exit Exit the CLI.
help Show help messages.
import Import data from csv file with headers and insert into target...
list List collections, partitions and indexes.
load Load specified collection.
query Query with a set of criteria, and results in a list of...
release Release specified collection.
search Conducts a vector similarity search with an optional boolean...
show Show connection, loading_progress and index_progress.
version Get Milvus CLI version.
milvus_cli > exit
Implementar manualmente o autocomplete
Diferente do autocomplete do shell do click, nosso projeto envolve a linha de comando e usa um loop para obter a entrada do usuário para implementar uma linha de comando rápida. Portanto, precisamos vincular um complemento a readline
.
class Completer(object):
RE_SPACE = re.compile('.*\s+$', re.M)
CMDS_DICT = {
'clear': [],
'connect': [],
'create': ['collection', 'partition', 'index'],
'delete': ['collection', 'partition', 'index'],
'describe': ['collection', 'partition'],
'exit': [],
'help': [],
'import': [],
'list': ['collections', 'partitions', 'indexes'],
'load': [],
'query': [],
'release': [],
'search': [],
'show': ['connection', 'index_progress', 'loading_progress'],
'version': [],
}
def __init__(self) -> None:
super().__init__()
self.COMMANDS = list(self.CMDS_DICT.keys())
self.createCompleteFuncs(self.CMDS_DICT)
def createCompleteFuncs(self, cmdDict):
for cmd in cmdDict:
sub_cmds = cmdDict[cmd]
complete_example = self.makeComplete(cmd, sub_cmds)
setattr(self, 'complete_%s' % cmd, complete_example)
def makeComplete(self, cmd, sub_cmds):
def f_complete(args):
f"Completions for the {cmd} command."
if not args:
return self._complete_path('.')
if len(args) <= 1 and not cmd == 'import':
return self._complete_2nd_level(sub_cmds, args[-1])
return self._complete_path(args[-1])
return f_complete
def _listdir(self, root):
"List directory 'root' appending the path separator to subdirs."
res = []
for name in os.listdir(root):
path = os.path.join(root, name)
if os.path.isdir(path):
name += os.sep
res.append(name)
return res
def _complete_path(self, path=None):
"Perform completion of filesystem path."
if not path:
return self._listdir('.')
dirname, rest = os.path.split(path)
tmp = dirname if dirname else '.'
res = [os.path.join(dirname, p)
for p in self._listdir(tmp) if p.startswith(rest)]
# more than one match, or single match which does not exist (typo)
if len(res) > 1 or not os.path.exists(path):
return res
# resolved to a single directory, so return list of files below it
if os.path.isdir(path):
return [os.path.join(path, p) for p in self._listdir(path)]
# exact file match terminates this completion
return [path + ' ']
def _complete_2nd_level(self, SUB_COMMANDS=[], cmd=None):
if not cmd:
return [c + ' ' for c in SUB_COMMANDS]
res = [c for c in SUB_COMMANDS if c.startswith(cmd)]
if len(res) > 1 or not (cmd in SUB_COMMANDS):
return res
return [cmd + ' ']
def complete(self, text, state):
"Generic readline completion entry point."
buffer = readline.get_line_buffer()
line = readline.get_line_buffer().split()
# show all commands
if not line:
return [c + ' ' for c in self.COMMANDS][state]
# account for last argument ending in a space
if self.RE_SPACE.match(buffer):
line.append('')
# resolve command to the implementation function
cmd = line[0].strip()
if cmd in self.COMMANDS:
impl = getattr(self, 'complete_%s' % cmd)
args = line[1:]
if args:
return (impl(args) + [None])[state]
return [cmd + ' '][state]
results = [
c + ' ' for c in self.COMMANDS if c.startswith(cmd)] + [None]
return results[state]
Depois de definir Completer
, podemos associá-lo ao readline:
comp = Completer()
def runCliPrompt():
try:
while not quitapp:
import readline
readline.set_completer_delims(' \t\n;')
readline.parse_and_bind("tab: complete")
readline.set_completer(comp.complete)
astr = input('milvus_cli > ')
try:
cli(astr.split())
except SystemExit:
# trap argparse error message
# print('error', SystemExit)
continue
except KeyboardInterrupt:
sys.exit(0)
Adicionar opção única
Para a linha de comando de prompt, às vezes não queremos executar completamente os scripts para obter algumas informações, como a versão. Um bom exemplo é Python
, quando digitamos python
no terminal, a linha de comando promtp será exibida, mas só retorna uma mensagem de versão e não entrará nos scripts do prompt se digitarmos python -V
. Assim, podemos utilizar sys.args
no nosso código para implementar.
def runCliPrompt():
args = sys.argv
if args and (args[-1] == '--version'):
print(f"Milvus Cli v{getPackageVersion()}")
return
try:
while not quitapp:
import readline
readline.set_completer_delims(' \t\n;')
readline.parse_and_bind("tab: complete")
readline.set_completer(comp.complete)
astr = input('milvus_cli > ')
try:
cli(astr.split())
except SystemExit:
# trap argparse error message
# print('error', SystemExit)
continue
except KeyboardInterrupt:
sys.exit(0)
if __name__ == '__main__':
runCliPrompt()
Obtemos sys.args
antes do loop quando corremos pela primeira vez para os scripts CLI. Se os últimos argumentos forem --version
, o código devolverá a versão do pacote sem entrar no ciclo.
Será útil depois de construirmos os códigos como um pacote. O utilizador pode escrever milvus_cli
para saltar para um prompt CLI, ou escrever milvus_cli --version
para obter apenas a versão.
Construir e lançar
Finalmente queremos construir um pacote e liberar pelo PYPI. Assim, o usuário pode simplesmente usar pip install <package name>
para instalar.
Instalar localmente para teste
Antes de publicar o pacote no PYPI, pode-se querer instalá-lo localmente para alguns testes.
Neste caso, pode simplesmente cd
no diretório do pacote e correr pip install -e .
(Não esquecer .
).
Criar ficheiros de pacote
Consulte: https://packaging.python.org/tutorials/packaging-projects/
A estrutura de um pacote deve ser semelhante:
package_example/
├── LICENSE
├── README.md
├── setup.py
├── src/
│ ├── __init__.py
│ ├── main.py
│ └── scripts/
│ ├── __init__.py
│ └── example.py
└── tests/
Criar o diretório do pacote
Crie o diretório Milvus_cli
com a estrutura abaixo:
Milvus_cli/
├── LICENSE
├── README.md
├── setup.py
├── milvus_cli/
│ ├── __init__.py
│ ├── main.py
│ ├── utils.py
│ └── scripts/
│ ├── __init__.py
│ └── milvus_cli.py
└── dist/
Escrever o código de entrada
A entrada do script deve estar em Milvus_cli/milvus_cli/scripts
, e o Milvus_cli/milvus_cli/scripts/milvus_cli.py
deve ser como:
import sys
import os
import click
from utils import PyOrm, Completer
pass_context = click.make_pass_decorator(PyOrm, ensure=True)
@click.group(no_args_is_help=False, add_help_option=False, invoke_without_command=True)
@click.pass_context
def cli(ctx):
"""Milvus CLI"""
ctx.obj = PyOrm()
"""
...
Here your code.
...
"""
@cli.command('exit')
def quitapp():
"""Exit the CLI."""
global quitapp
quitapp = True
quitapp = False # global flag
comp = Completer()
def runCliPrompt():
args = sys.argv
if args and (args[-1] == '--version'):
print(f"Milvus Cli v{getPackageVersion()}")
return
try:
while not quitapp:
import readline
readline.set_completer_delims(' \t\n;')
readline.parse_and_bind("tab: complete")
readline.set_completer(comp.complete)
astr = input('milvus_cli > ')
try:
cli(astr.split())
except SystemExit:
# trap argparse error message
# print('error', SystemExit)
continue
except Exception as e:
click.echo(
message=f"Error occurred!\n{str(e)}", err=True)
except KeyboardInterrupt:
sys.exit(0)
if __name__ == '__main__':
runCliPrompt()
Editar o diretório setup.py
from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
setup(
name='milvus_cli',
version='0.1.6',
author='Milvus Team',
author_email='milvus-team@zilliz.com',
url='https://github.com/milvus-io/milvus_cli',
description='CLI for Milvus',
long_description=long_description,
long_description_content_type='text/markdown',
license='Apache-2.0',
packages=find_packages(),
include_package_data=True,
install_requires=[
'Click==8.0.1',
'pymilvus==2.0.0rc5',
'tabulate==0.8.9'
],
entry_points={
'console_scripts': [
'milvus_cli = milvus_cli.scripts.milvus_cli:runCliPrompt',
],
},
python_requires='>=3.8'
)
Algumas dicas aqui:
- Usamos o conteúdo de
README.md
como a descrição longa do pacote. - Adicione todas as dependências a
install_requires
. - Especifique o
entry_points
. Neste caso, definimosmilvus_cli
como um filho deconsole_scripts
, para que possamos digitarmilvus_cli
como um comando diretamente após instalarmos este pacote. E o ponto de entrada demilvus_cli
é a funçãorunCliPrompt
emmilvus_cli/scripts/milvus_cli.py
.
Construir
Actualize o pacote
build
:python3 -m pip install --upgrade build
Execute build:
python -m build --sdist --wheel --outdir dist/ .
Serão gerados dois ficheiros no diretório
dist/
:
dist/
example_package_YOUR_USERNAME_HERE-0.0.1-py3-none-any.whl
example_package_YOUR_USERNAME_HERE-0.0.1.tar.gz
Publicar versão
Consulte: https://packaging.python.org/tutorials/packaging-projects/#uploading-the-distribution-archives
- Atualizar o pacote
twine
:python3 -m pip install --upgrade twine
- Upload para
PYPI
test env:python3 -m twine upload --repository testpypi dist/*
- Fazer upload para
PYPI
:python3 -m twine upload dist/*
CI/CD por fluxos de trabalho do Github
Consultar: https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/
Queremos uma maneira de fazer upload de ativos automaticamente, ele pode construir os pacotes e enviá-los para lançamentos do github e PYPI.
(Por alguma razão, queremos apenas que o fluxo de trabalho publique o lançamento para testar o PYPI).
# This is a basic workflow to help you get started with Actions
name: Update the release's assets after it published
# Controls when the workflow will run
on:
release:
# The workflow will run after release published
types: [published]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.8'
architecture: 'x64'
- name: Install pypa/build
run: >-
python -m
pip install
build
--user
- name: Clean dist/
run: |
sudo rm -fr dist/*
- name: Build a binary wheel and a source tarball
run: >-
python -m
build
--sdist
--wheel
--outdir dist/
.
# Update target github release's assets
- name: Update assets
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: ./dist/*
- name: Publish distribution 📦 to Test PyPI
if: contains(github.ref, 'beta') && startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
repository_url: https://test.pypi.org/legacy/
packages_dir: dist/
verify_metadata: false
Saiba mais sobre o Milvus
Milvus é uma ferramenta poderosa capaz de alimentar uma vasta gama de aplicações de inteligência artificial e pesquisa de similaridade vetorial. Para saber mais sobre o projeto, consulte os seguintes recursos:
- Agrupar comandos
- Personalizar um comando
- Implementar um prompt CLI para o utilizador introduzir dados
- Implementar manualmente o autocomplete
- Adicionar opção única
- Construir e lançar
- Saiba mais sobre o Milvus
On This Page
Try Managed Milvus for Free
Zilliz Cloud is hassle-free, powered by Milvus and 10x faster.
Get StartedLike the article? Spread the word