Todos os produtos
Search
Central de documentação

Vector Retrieval Service for Milvus:Crie um aplicativo de recuperação de casos jurídicos com a base de conhecimento do Alibaba Cloud Milvus

Última atualização: Aug 27, 2026

A base de conhecimento do Alibaba Cloud Milvus transforma leis e regulamentos públicos, decisões judiciais e políticas de conformidade corporativa em um assistente pesquisável de materiais jurídicos: recupere casos semelhantes pelos detalhes do caso, resuma opiniões judiciais e exiba as fontes originais.

Solução

O pipeline de ponta a ponta é idêntico ao descrito em Build an intelligent customer service Q&A application by using the Alibaba Cloud Milvus knowledge base: defina tags no console → importe documentos de source em lotes por tag → publique uma versão → recuperação via SDK com filtragem opcional por tag → um LLM gera respostas com citações de fontes → o Flask serve a página de perguntas e respostas. Reutilize diretamente o código de engenharia daquele tutorial: kb_client.py, upload.py, app.py, templates/index.html e start.sh. Este tópico aborda apenas as partes que exigem alteração para um cenário jurídico: tags de caso (incluindo um tipo de tag numérico), parâmetros de recuperação, restrições de prompt e o tratamento de resultados vazios obrigatório.

Para a primeira versão, escolha um único tipo de crime ou tópico de conformidade e importe apenas de 10 a 50 documentos de source. Expanda o escopo após a validação. Todo o processo leva cerca de 25 a 40 minutos. O tempo de análise dos documentos depende da quantidade e do tamanho dos arquivos de source.

Esta solução é uma ferramenta de apoio à recuperação de materiais e não emite pareceres jurídicos. Mantenha uma etapa de revisão manual tanto para os resultados da recuperação quanto para os resumos gerados pelo modelo.

Pré-requisitos

  • Crie uma base de conhecimento do Milvus e registre o ID da base de conhecimento (por exemplo, kd-803ae9b10cc31).

  • Crie um usuário RAM na sua conta Alibaba Cloud, selecione Use permanent AccessKey e anexe a política de sistema AliyunMilvusFullAccess.

  • Prepare o endpoint e a chave de API de um modelo de linguagem grande compatível com o protocolo OpenAI chat/completions.

  • Instale o Python 3.8 ou posterior localmente e configure o diretório do projeto seguindo as instruções.

  • Conclua a verificação de autorização e a desidentificação dos seus documentos de source. Para mais detalhes, consulte Notes for legal scenarios.

Etapa 1: Definir tags de caso

Na página de detalhes da base de conhecimento, abaixo de Basic Information, escolha Tags > Gerencie e adicione as quatro tags a seguir. Selecione o tipo de campo na lista suspensa à direita da caixa de entrada do nome da tag. As opções disponíveis são string, int64, list, float32 e bool.

Nome da tag

Tipo de campo

Descrição

Valores de exemplo

docType

string

Tipo de documento

Decisão judicial, Leis e regulamentos, Política de conformidade

court

string

Tribunal do julgamento

O Primeiro Tribunal Popular da Cidade XX

caseType

string

Causa da ação

Fraude contratual, Causar acidente de trânsito

judgmentYear

int64

Ano da decisão

2025

Definir o ano da decisão como int64 não habilita consultas de intervalo. A API de recuperação não suporta expressões de intervalo de inteiros. O objetivo de definir o ano da decisão como int64 é permitir que o servidor converta strings numéricas em inteiros, garantindo que =, in e not in funcionem corretamente. Portanto, para um requisito como "casos dos últimos três anos", o cliente deve primeiro calcular a lista de anos e depois passá-la com in, por exemplo [2024, 2025, 2026]. Não use ou nas condições de tag_filter. Quando o intervalo de anos for grande, não há alternativa igualmente eficiente — divida-o em várias consultas.

Aviso

A caixa de diálogo de gerenciamento de tags é salva como um todo, e a lista de tags carrega de forma assíncrona após a abertura da caixa. Aguarde até que todas as tags existentes sejam exibidas antes de adicionar novas tags e clicar em Done. Caso contrário, o salvamento pode sobrescrever tudo com uma "lista vazia + novas tags", apagando as definições de tags existentes.

Observe os seguintes pontos sobre tipos e valores de tags:

  • Para uma tag do tipo int64, escreva um número JSON simples (como 2025) diretamente em documents.jsonl. Durante a filtragem, value corresponde independentemente de você passar um número ou uma string.

  • Os valores das tags podem ser uma string vazia. Leis, regulamentos e políticas de conformidade não possuem tribunal de julgamento, então escreva "court": "". O console exibe isso como court=, e você pode usar posteriormente court = "" como condição de filtro para corresponder exatamente a esses documentos de source.

Etapa 2: Preparar documentos de source e o manifesto de importação

  • Coloque os documentos de source no diretório documents/. Os formatos PDF, DOCX, Markdown, TXT e outros são suportados. Mantenha o número do caso, o tribunal e a data da decisão no corpo do texto ou no nome do arquivo. Em nossos testes, após escrever o número do caso na primeira linha do corpo, conseguimos recuperar a decisão correspondente diretamente pelo número do caso.

  • Crie o arquivo documents.jsonl e anote cada documento de source com o tipo de documento, tribunal, causa da ação e ano da decisão:

    {"path": "documents/criminal-case-001.md", "metadata": {"docType": "Court judgment", "court": "The First People's Court of XX City", "caseType": "Contract fraud", "judgmentYear": 2025}}
    {"path": "documents/criminal-case-002.md", "metadata": {"docType": "Court judgment", "court": "The Second People's Court of XX City", "caseType": "Contract fraud", "judgmentYear": 2023}}
    {"path": "documents/law-excerpt.md", "metadata": {"docType": "Laws and regulations", "court": "", "caseType": "Criminal", "judgmentYear": 2024}}
    {"path": "documents/compliance-policy.md", "metadata": {"docType": "Compliance policy", "court": "", "caseType": "Compliance", "judgmentYear": 2024}}
  • As decisões judiciais devem preservar o contexto dos fatos do caso, a fundamentação da decisão e a conclusão. Na página de detalhes da base de conhecimento, clique em Create Policy em Processing Policy para ajustar a granularidade dos fragmentos. A unidade do comprimento máximo do fragmento é caracteres (padrão 512). Defina entre 770 e 1.150 caracteres (aproximadamente 512 a 768 tokens) para que as questões-chave em disputa e a fundamentação da decisão caiam no mesmo fragmento sempre que possível.

  • Importe casos com conclusões diferentes sob a mesma causa de ação. O valor da recuperação jurídica reside justamente em apresentar divergências. Em nossos testes, após importar duas decisões de fraude contratual com conclusões opostas, o modelo citou ambas e apontou explicitamente que "um caso reconheceu crime conjunto, enquanto o outro não devido à falta de provas de conspiração". Isso é mais valioso para referência do que importar materiais com uma única conclusão. Exija no prompt que tais casos sejam apresentados separadamente, para evitar que o modelo apresente a conclusão de um caso individual como regra geral.

Etapa 3: Configurar parâmetros de recuperação e o prompt

Em config.json, ajuste aliyun.knowledge_base_version, retrieval e scenario para o cenário jurídico. O exemplo a seguir mostra apenas esses três blocos. Mantenha os blocos restantes do seu config.json do tutorial referenciado, incluindo o ID da base de conhecimento, o endpoint e a chave de API do LLM preparados nos Pré-requisitos.

{
  "aliyun": {
    "knowledge_base_version": "LATEST_PUBLISHED"
  },
  "retrieval": {
    "page_size": 8,
    "candidate_count": 80,
    "min_score": 0.25,
    "semantic_weight": 0.4,
    "enable_query_expansion": true,
    "rerank_model_name": "qwen3-rerank",
    "tag_filter": {
      "relation": "and",
      "conditions": [
        {"field": "docType", "op": "=", "value": "Court judgment"}
      ]
    }
  },
  "scenario": {
    "title": "Legal and compliance case retrieval",
    "system_prompt": "You are a legal materials retrieval assistant. Do not provide final legal opinions. Summarize facts, key issues in dispute, and judicial opinions and their bases strictly from the retrieved materials, and annotate each item with [Source N]. When different cases reach conflicting conclusions, present them separately. If the retrieved materials are empty or irrelevant to the question, reply only with 'Insufficient materials; manual review is recommended', and never cite any law, regulation, judicial interpretation, or case that does not appear in the retrieved materials.",
    "image_enabled": false,
    "sample_questions": [
      "When someone defrauds payment for goods by fabricating the ability to perform a contract, how is joint crime in contract fraud determined?",
      "How does voluntary surrender after causing a traffic accident affect sentencing?",
      "Which cases discuss the distinction between a principal offender and an accessory?"
    ]
  }
}

Observe os seguintes pontos sobre os parâmetros principais:

  • semantic_weight=0.4: a recuperação jurídica faz uso intenso de números de casos, tipos de crimes e terminologia jurídica; portanto, reduza o peso semântico e aumente proporcionalmente o peso de palavras-chave. Em nossos testes, pesquisar com um número de caso completo recuperou a decisão correspondente com precisão. Ajuste esse valor usando scoreDetails nos resultados da recuperação, que contém keywordScore e semanticScore.

  • candidate_count=80 com qwen3-rerank ativado: expanda o conjunto de candidatos e permita que o modelo de reclassificação os ordene por similaridade com casos semelhantes. Note que as pontuações de rerank e as pontuações vetoriais não estão na mesma escala. Combinadas com min_score, elas podem filtrar alguns resultados. Ao ajustar, fixe um dos dois valores primeiro.

  • Mantenha knowledge_base_version definido como LATEST_PUBLISHED, a menos que o rastreamento de conformidade exija uma versão fixa. Para detalhes sobre fixação e exclusão de versões, consulte Manage published versions.

Apenas três operadores funcionam para filtragem de tags

Em nossos testes na versão atual, apenas =, in e not in em tag_filter.conditions têm efeito real como valor de op**:

Operador

Comportamento

Exemplo

=

Correspondência exata; para uma tag int64, passar um número ou uma string funciona

{"field": "judgmentYear", "op": "=", "value": 2025}

in

Correspondência enumerada

{"field": "judgmentYear", "op": "in", "value": [2024, 2025]}

not in

Excluir uma enumeração

{"field": "docType", "op": "not in", "value": ["Compliance policy"]}

>, , <,

A condição é ignorada e todos os dados são retornados (nenhum erro é gerado)

, empty, not empty, start with, end with

A condição é ignorada e todos os dados são retornados

contains, not contains

Aliases de in/not in, não indicam contenção de string; passar um fragmento de string retorna 0 resultados

Se você passar um operador ineficaz, a API não gera erro e retorna todos os dados sem filtragem. Em um cenário jurídico, isso significa que casos que deveriam ter sido excluídos ainda entram no contexto do LLM, sem que nada na página pareça errado. Portanto:

  • Não use > ou para filtrar por intervalo de anos. Use in com uma enumeração de anos, por exemplo {"field": "judgmentYear", "op": "in", "value": [2023, 2024, 2025]}.

  • Não use empty para verificar uma tag vazia. Use {"field": "court", "op": "=", "value": ""} em vez disso.

  • Após configurar uma condição de filtro, sempre compare a contagem total de resultados com o resultado não filtrado para confirmar que o filtro teve efeito. Se ambos forem idênticos, a condição não foi aplicada.

    Se op for escrito como um alias, como eq, == ou like, a API retorna 400 Unsupported tag filter operator, e todas as perguntas falham. Os "Operadores suportados" listados nessa mensagem de erro incluem operadores da tabela acima que não têm efeito; portanto, não servem como lista utilizável.

Etapa 4: Interceptar resultados de recuperação vazios em app.py

No app.py reutilizado, llm_answer() ainda chama o LLM quando os resultados da recuperação estão vazios. O contexto torna-se uma string vazia, e o modelo responde inteiramente com base em seu próprio conhecimento. Em nossos testes, fizemos uma pergunta não coberta pela base de conhecimento, como "Como são calculados os danos por violação de propriedade intelectual?". O modelo produziu longas regras de cálculo de danos e fabricou anotações de source como [Fonte 1: Interpretação de ... Danos Punitivos, Artigo 2], mesmo com sources vazio. Em um cenário jurídico, esse tipo de saída é altamente enganosa e exige interceptação no nível do código:

def llm_answer(question: str, results: list[dict[str, Any]]) -> str:
    if not LLM.get("enabled", True):
        return "The LLM is disabled; please see the retrieval results below."
    if not results:
        return "No materials related to this question were found in the knowledge base, so I cannot answer. Add relevant materials and try again, or escalate to manual review."
    ...

As restrições de prompt sozinhas não são confiáveis. Mesmo quando system_prompt diz explicitamente "declare claramente quando os materiais forem insuficientes", o modelo ainda responde. Após adicionar o fallback acima, perguntas não cobertas retornam consistentemente a mensagem de prompt, e perguntas normais com resultados de recuperação permanecem inalteradas.

Etapa 5: Carregar, publicar e verificar

  1. Carregue os documentos de source. MetaFields aplica-se a todo o lote; o script agrupa documentos por tag e os envia em lotes.

    python upload.py --manifest documents.jsonl
  2. Na página Data Management do console, confirme se o status do documento é Processed. Uma resposta bem-sucedida da API de upload significa apenas que a análise assíncrona foi enviada.

  3. Na página Version Management, clique em Publish Version, conclua o assistente e confirme se o status da nova versão é Published. Se o botão estiver acinzentado, exclua primeiro as versões antigas desnecessárias. Consulte Manage published versions.

  4. Inicie o service.

    python app.py
  5. Após iniciar o service, faça uma pergunta de teste:

    curl -sS http://127.0.0.1:7860/api/ask \
      -H 'Content-Type: application/json' \
      -d '{"question":"When someone defrauds payment for goods by fabricating the ability to perform a contract, how is joint crime in contract fraud determined?"}'
  • A página abre normalmente, e o envio de uma pergunta retorna tanto a resposta quanto as fontes de recuperação.

  • Cada [Source N] na resposta corresponde a um fragmento de source na seção de fontes abaixo.

  • Pesquisar com um número de caso completo recupera a decisão judicial correspondente com precisão.

  • Ao perguntar sobre conteúdo não coberto pela base de conhecimento, a resposta retorna a mensagem "Materiais insuficientes" e não contém nenhuma citação jurídica. Certifique-se de testar isso na prática — é o critério de aceitação mais crítico para este cenário.

  • Após adicionar mais materiais e publicar uma nova versão, a página consegue recuperar o novo conteúdo.

Gerenciar versões publicadas

LATEST_PUBLISHED usa automaticamente a versão publicada mais recente. Você também pode especificar um número de versão explícito (como v2) para fixar uma versão. Cenários de conformidade geralmente exigem rastrear qual versão dos materiais apoiou determinada decisão; portanto, registre o número da versão efetivamente usado nos logs de chamadas do aplicativo.

Importante

Após a exclusão, uma versão torna-se imediatamente irrecuperável, e o backend limpa os dados de forma assíncrona posteriormente. Antes de excluir uma versão, migre qualquer aplicativo que ainda esteja fixado nela para uma nova versão. Se você excluir uma versão em uso pelo aplicativo, a recuperação retorna imediatamente 404 Knowledge base version ... does not exist, e todas as perguntas falham na página.

No máximo três versões publicadas podem existir simultaneamente. Ao atingir o limite, o botão Publish Version fica acinzentado, enquanto a página ainda mostra "There are N pending changes". Exclua primeiro as versões antigas desnecessárias dos registros de versão. Se você fixar uma versão, estabeleça também um processo de atualização da configuração antes de excluir uma versão antiga.

Observações para cenários jurídicos

Antes de compartilhar o aplicativo com os usuários finais, revise as seguintes considerações:

  • Apenas materiais autorizados — Use somente materiais que você tem autorização para tornar públicos e processar, e conclua a verificação de autorização antes do upload. Se uma decisão judicial contiver informações pessoais, desidentifique-as primeiro. Fragmentos de documentos são enviados ao LLM como contexto, o que constitui saída de dados.

  • Aviso legal permanente — A página deve conter um aviso legal permanente. A página de exemplo diz apenas "As respostas são geradas a partir do conteúdo publicado da base de conhecimento". Altere o aviso em templates/index.html para declarar claramente: "Estes resultados são apenas um auxílio à recuperação de materiais e não constituem pareceres jurídicos. Submeta-os à revisão por um profissional."

  • Pontos de entrada separados por tipo de documento — Por exemplo, restrinja um ponto de entrada de consulta pública com docType = Laws and regulations para recuperar apenas leis e regulamentos públicos, enquanto um ponto de entrada de análise interna também permite decisões judiciais.

  • Implantação em produção — Não continue usando o servidor de desenvolvimento Flask. Migre para um servidor WSGI de produção e adicione gerenciamento de segredos, autenticação, auditoria e limitação de taxa.

Perguntas frequentes

Sintoma

Causa e solução

Todas as perguntas retornam 404 Knowledge base version ... does not exist

O número da versão codificado na configuração foi excluído ou nunca foi publicado. Use LATEST_PUBLISHED ou especifique um nome de versão que realmente exista na página Version Management.

Perguntas retornam 500, e os logs mostram 400 Unsupported tag filter operator

op usa um alias como eq, == ou like. Use =, in ou not in em vez disso.

A contagem de resultados com filtragem de tags é exatamente a mesma sem ela

Você usou um operador ineficaz (como >, , ou empty). Use =, in ou not in em vez disso. Consulte a Etapa 3.

A filtragem por intervalo de anos não tem efeito

Operadores de comparação numérica não têm efeito. Use in com uma enumeração de anos.

A filtragem de tags sempre retorna 0 resultados

O nome da tag está escrito de forma diferente do registrado. A API não gera erro e apenas retorna 0 resultados. Verifique em Tags > Gerencie na página de detalhes da base de conhecimento. Em nossos testes, o operador contains também retorna 0 resultados.

Fez uma pergunta não coberta pela base de conhecimento, mas obteve citações jurídicas com aparência plausível

llm_answer() ainda chamou o LLM quando os resultados da recuperação estavam vazios. Adicione o fallback de resultado vazio da Etapa 4.

O campo tags nos resultados da recuperação está vazio

A versão atual não preenche retroativamente as tags. O campo tags não retorna o tribunal e o ano da decisão informados no momento do upload. É um campo diferente dos MetaFields de AddDocuments, e não há parâmetro para habilitar seu retorno. Um valor vazio não significa que o upload falhou. Se precisar mostrar essas informações nas respostas, junte os resultados da recuperação com documents.jsonl por documentId e anexe ao contexto.

O upload retorna 400 No OSS document can be registered.

Todos os arquivos no lote foram deduplicados devido a nomes duplicados. Renomeie os arquivos ou exclua os dados antigos no console primeiro.

A recuperação relata falha com code = None

A lógica _check() no código reutilizado está incorreta (getattr(body, "code", 0) != 0): uma resposta bem-sucedida tem code = None. Altere para getattr(body, "code", None) not in (None, 0, "0").

Após ativar o reranking, há menos resultados

As pontuações de rerank e as pontuações vetoriais estão em escalas diferentes. Combinadas com min_score, mais resultados são filtrados. Reduza min_score primeiro ou desative o reranking para comparar.