Uma base de conhecimento do Alibaba Cloud Milvus transforma políticas, processos e SOPs dispersos entre departamentos em um ponto de entrada unificado para perguntas e respostas internas. Funcionários fazem perguntas em linguagem natural e recebem as etapas dos processos junto com as citações originais, enquanto tags de departamento restringem a recuperação a um escopo específico.
Visão geral da solução
O pipeline de ponta a ponta é idêntico ao descrito em Build an intelligent customer service Q&A application with Alibaba Cloud Milvus knowledge base: defina tags no console → importe documentos em lote por tag → publique uma versão → recupere dados pelo SDK com filtragem opcional por tag → um LLM gera respostas com citações de source → o Flask serve a página de perguntas e respostas. Reutilize diretamente o código desse documento (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 o cenário de políticas: o esquema de tags de departamento, os parâmetros de recuperação, o prompt do assistente de políticas e o gerenciamento de versões.
Para a primeira versão, escolha de 5 a 20 documentos de um único departamento e expanda após verifique a qualidade da recuperação e do prompt. Todo o processo leva cerca de 20 a 30 minutos.
Pré-requisitos
Crie uma base de conhecimento e anote o Knowledge Base ID (por exemplo,
kd-803ae9b10cc31).Crie um usuário RAM com sua conta Alibaba Cloud, selecione Use a permanent AccessKey pair e anexe a política de sistema
AliyunMilvusFullAccess.Tenha um endpoint de LLM compatível com o protocolo
chat/completionsda OpenAI e sua chave de API.Instale o Python 3.9 ou posterior localmente e configure o diretório do projeto seguindo o tutorial referenciado na Visão geral da solução.
Etapa 1: Definir tags de departamento e de política
Na página de detalhes da base de conhecimento, em Basic Information, escolha Tags > Manage e adicione as três tags a seguir, todas do tipo string:
|
Tag name |
Description |
Example value |
|
department |
O departamento proprietário da política |
Finance, Administration, IT |
|
docType |
O tipo de documento |
Policy, Process |
|
effectiveDate |
A data de vigência |
2026-01-01 |
Os valores das tags aceitam texto arbitrário; portanto, use diretamente os nomes do departamento e do tipo de documento.
A caixa de diálogo de gerenciamento de tags é salva como um todo. Ao abrir a caixa de diálogo, a lista de tags carrega de forma assíncrona. Aguarde a exibição de todas as tags existentes antes de adicionar novas tags e clique em Done. Caso contrário, a caixa de diálogo poderá sobrescrever tudo com uma lista vazia mais as novas tags, excluindo as definições de tags existentes.
Os valores de tags já gravados nos dados não são perdidos. Após readicionar as tags, verifique a contagem de tags na página de detalhes.
As definições de tags servem principalmente para padronizar os valores permitidos. Nomes de tags não definidos ainda podem ser gravados com dados e usados para filtragem, mas defina-os antes da importação para facilitar a colaboração e a manutenção da equipe.
Etapa 2: Organizar os documentos de política e o manifesto de importação
Organize os documentos por departamento e coloque-os no diretório
documents/. Inclua o nome da política e a versão ou data de vigência no nome de cada arquivo para facilitar a identificação na seção de fontes das respostas.-
Crie o arquivo
documents.jsonle anote cada documento com o departamento, o tipo e a data de vigência:{"path": "documents/finance-travel.md", "metadata": {"department": "Finance", "docType": "Policy", "effectiveDate": "2026-01-01"}} {"path": "documents/finance-reimbursement.md", "metadata": {"department": "Finance", "docType": "Process", "effectiveDate": "2026-02-01"}} {"path": "documents/hr-leave.md", "metadata": {"department": "Administration", "docType": "Policy", "effectiveDate": "2026-01-01"}} {"path": "documents/it-troubleshoot.md", "metadata": {"department": "IT", "docType": "Process", "effectiveDate": "2026-03-01"}}
Escreva cada documento de política com a data de vigência e o responsável no início do corpo, e marque versões antigas com "Superseded by version X". Por padrão, o LLM vê apenas o corpo dos chunks recuperados. A tag
effectiveDatenão é adicionada automaticamente ao prompt e só é incluída após você aplicar as alterações na Etapa 4.Ajuste a granularidade de chunking. Documentos de política geralmente são entradas curtas. Na página de detalhes da base de conhecimento, em Processing Policy (configurações de processamento e chunking de documentos), clique em Create Policy. A unidade de comprimento máximo do chunk é caracteres (padrão 512). Defina entre 580 e 770 caracteres (cerca de 384 a 512 tokens) para manter cada etapa do processo intacta.
Etapa 3: Configurar os parâmetros de recuperação e o prompt
No arquivo config.json, ajuste retrieval e scenario para o cenário de políticas. Modifique apenas essas duas seções e mantenha o restante do arquivo inalterado.
{
"retrieval": {
"page_size": 6,
"candidate_count": 48,
"min_score": 0.35,
"semantic_weight": 0.6,
"enable_query_expansion": true,
"rerank_model_name": "",
"tag_filter": {
"relation": "and",
"conditions": []
}
},
"scenario": {
"title": "Enterprise policy and process Q&A",
"system_prompt": "You are an internal policy assistant. Answer only based on the retrieved published policies. Organize processes into steps, state the applicable conditions and required materials, and cite sources as [Source N]. When the materials conflict or are insufficient, clearly tell the user to contact the policy owner.",
"image_enabled": false,
"sample_questions": [
"What materials are required for travel expense reimbursement?",
"Who approves leave requests longer than three days?",
"What is the process for reporting an issue when my computer cannot connect to the network?"
]
}
}
Os outros parâmetros no exemplo (page_size, candidate_count, enable_query_expansion e rerank_model_name) mantêm os valores do tutorial referenciado. Os parâmetros min_score, semantic_weight e tag_filter exigem escolhas específicas para o cenário:
-
min_score— Não existe um valor recomendado universal para todos os corpus. Calibre o limiar em relação ao seu próprio corpus com perguntas reais:Defina
min_scorecomo 0 e recupere um lote de resultados.Rotule manualmente a relevância dos resultados.
Escolha um limiar com base na distribuição de recall e falsos positivos. Até obter esses dados de calibração, comece com 0,35, valor medido no corpus deste tópico. Nesse corpus, com 0,2, políticas totalmente não relacionadas à pergunta (pontuações de 0,36 a 0,43) ainda entravam no contexto do LLM, aumentando o custo e o risco de respostas incorretas. Com 0,35, esses resultados foram filtrados. Recalibre sempre que alterar o corpus, trocar o modelo de rerank ou ajustar
semantic_weight.
-
semantic_weight=0.6— Perguntas sobre políticas frequentemente misturam substantivos próprios com expressões coloquiais; portanto, equilibre a correspondência semântica e por palavras-chave.Este parâmetro é o fator de ponderação da pontuação final, calculada como
score ≈ (1-semantic_weight) × keywordScore + semantic_weight × semanticScore(um rank feature também pode ser adicionado). O parâmetromin_scorefiltra com base nessa pontuação final após seu cálculo.Sem reranking,
semanticScoreé a similaridade vetorial; com reranking ativado, é a pontuação do modelo de rerank. As duas têm escalas diferentes, portanto **após alterar o peso ou alternar o reranking, recalibre o limiar juntamente comscoreDetails**. Reduzir o peso não necessariamente diminui a pontuação total; a pontuação cai apenas quando semanticScore é maior que keywordScore para o lote. tag_filter— Deixeconditionsem branco por padrão para recuperar dados de todos os departamentos. Configure condições de filtro apenas quando quiser limitar um ponto de entrada a um único departamento. A Etapa 5: Restringir a recuperação a um único departamento explica a configuração e lista os comportamentos dos operadores que você precisa conhecer.
Etapa 4: Incluir departamento e data de vigência nas respostas
Perguntas e respostas sobre políticas devem determinar qual versão está vigente e a qual departamento ela se aplica; portanto, inclua os valores das tags no contexto do LLM. O campo tags nos resultados de recuperação não retorna os valores de tag que você gravou. Ele vem de um recurso interno de aprimoramento de tags de chunk do service e é um campo diferente dos MetaFields gravados por AddDocuments. Nenhum parâmetro de solicitação pode ativar seu retorno, portanto um valor vazio não significa que o upload falhou ou que as tags foram perdidas.
Mantenha o mapeamento no lado do cliente: indexe o manifesto de importação local pelo nome do arquivo, pesquise cada resultado de recuperação pelo seu documentName e anexe os valores das tags ao contexto do LLM.
A pesquisa depende de correspondência exata de nomes. Verifique se o documentName retornado pela recuperação corresponde ao nome do arquivo em documents.jsonl. Caso contrário, a entrada de source carregará silenciosamente nenhum rótulo de tag.
Em app.py, após app = Flask(__name__), adicione o mapeamento:
META_BY_NAME: dict[str, dict[str, Any]] = {}
_manifest = Path("documents.jsonl")
if _manifest.is_file():
for _line in _manifest.read_text(encoding="utf-8").splitlines():
if _line.strip():
_entry = json.loads(_line)
META_BY_NAME[Path(str(_entry["path"])).name] = _entry.get("metadata") or {}
Em seguida, modifique llm_answer() para incluir os valores das tags ao construir o contexto e fornecer a data atual na pergunta:
def llm_answer(question: str, results: list[dict[str, Any]]) -> str:
if not LLM.get("enabled", True):
return "The LLM is disabled. Review the retrieval results below."
if not results:
return "The current policy materials contain no relevant provisions. Contact the corresponding policy owner for confirmation."
blocks = []
for index, item in enumerate(results, 1):
title = field(item, "documentName", "DocumentName")
meta = META_BY_NAME.get(title) or {}
label = ", ".join(f"{key}={value}" for key, value in meta.items())
header = f"[Source {index}] {title}" + (f" ({label})" if label else "")
blocks.append(f"{header}\n{field(item, 'content', 'Content')}")
context = "\n\n".join(blocks)
today = datetime.date.today().isoformat()
url = str(LLM["base_url"]).rstrip("/") + "/chat/completions"
response = requests.post(
url,
headers={"Authorization": f"Bearer {LLM['api_key']}"},
json={
"model": LLM["model"],
"temperature": 0.1,
"messages": [
{"role": "system", "content": SCENARIO.get("system_prompt", "Answer only based on the provided materials.")},
{"role": "user", "content": f"Current date: {today}\nQuestion: {question}\n\nRetrieved materials:\n{context}"},
],
},
timeout=90,
)
response.raise_for_status()
return response.json()["choices"][0]["message"]["content"].strip()
Adicione import datetime ao cabeçalho do arquivo.
Nunca pule a etapa de fornecer a data atual. O LLM não sabe a data de hoje. Se você fornecer apenas effectiveDate, o modelo assumirá uma data atual por conta própria e poderá chegar à conclusão oposta. Por exemplo, ele pode julgar uma nova versão já vigente como "ainda não vigente" e citar um padrão antigo substituído. Somente quando tanto os valores das tags quanto a data atual forem fornecidos o modelo poderá selecionar corretamente a versão atual e explicar que as versões antigas foram substituídas.
Etapa 5: Restringir a recuperação a um único departamento
Para fornecer um ponto de entrada dedicado a um departamento, configure a condição de filtro correspondente:
"tag_filter": {
"relation": "and",
"conditions": [
{"field": "department", "op": "=", "value": "Finance"}
]
}
Também é possível combinar várias condições. Por exemplo, para pesquisar apenas os documentos de processo do departamento Financeiro:
"conditions": [
{"field": "department", "op": "=", "value": "Finance"},
{"field": "docType", "op": "=", "value": "Process"}
]
Armadilhas dos operadores de filtro de tag
Apenas =, in e not in têm efeito real em um filtro de tag. Revise os seguintes comportamentos antes de configurar um filtro:
|
Configuration |
Actual behavior |
|
|
Testes mostram que a condição é ignorada silenciosamente e os dados completos não filtrados são retornados. |
|
|
Estes são apenas aliases das operações de conjunto |
|
|
A API não retorna erro e apenas 0 resultados. |
|
|
A API retorna |
Práticas corretas:
Para filtrar por intervalo numérico ou de data, enumere os valores com
in.Para verificar se uma tag está vazia, use
= "".Após configurar condições de filtro, compare o número total de resultados com e sem o filtro para confirme que o filtro tem efeito. Depois que um filtro de departamento é configurado, este ponto de entrada pode responder apenas a perguntas desse departamento. Ajuste
sample_questionsadequadamente; caso contrário, perguntas sobre outros departamentos retornarão apenas "nenhuma disposição relevante".
Para mudar um ponto de entrada para outro departamento, altere três locais simultaneamente:
Os metadados em
documents.jsonl, que são a source das tags gravadas no upload e dos rótulos de resposta adicionados na Etapa 4.tag_filteremconfig.json, que define o escopo de recuperação do ponto de entrada.As perguntas de exemplo, para que correspondam ao novo escopo.
Etapa 6: Fazer upload, publicar e verificar
Faça o upload dos documentos
MetaFields aplica-se a todo o lote. O script agrupa documentos por tag primeiro e os envia em lotes.
python upload.py --manifest documents.jsonl
Uploads duplicados de arquivos com o mesmo nome falham. Quando doc_name_dedup=True, se todos os arquivos do lote forem deduplicados por nome, a API retorna 400 No OSS document can be registered. Ao atualize políticas, inclua o número da versão no nome do arquivo ou exclua os dados antigos no console primeiro.
Verifique o resultado do processamento
Na página Data Management no console, confirme se o status do documento é Processing Completed e se a coluna de tags mostra as tags gravadas (por exemplo, docType=Process, department=IT +1).
Publique uma versão
Na página Version Management, clique em Publish Version. Após concluir o assistente de três etapas, confirme se o status da nova versão é Published.
Após atualize as políticas, republicar a versão é obrigatório. As aplicações recuperam novo conteúdo apenas quando usam LATEST_PUBLISHED.
Cota de versões. No máximo 3 versões publicadas podem existir simultaneamente por padrão. Esse limite é contado por tenant e não aumenta com a especificação CU da instância. Quando o limite é atingido, o botão Publish Version fica desativado, enquanto a página ainda mostra que há N alterações pendentes para publicação. Bases de conhecimento de políticas são atualizadas frequentemente. Mantenha apenas a versão vigente atual mais a versão histórica mais recente e limpe versões mais antigas antes de publicar uma nova. O limite pode ser ajustado no lado do servidor, mas atualmente não há entrada de solicitação de cota self-service para usuários. Abra um ticket para avaliação.
A exclusão de versão é irreversível, e uma versão excluída torna-se imediatamente irrecuperável. O backend limpa os dados de forma assíncrona. Antes da exclusão, confirme que nenhuma aplicação está bloqueada nesse número de versão e migre as aplicações que ainda a utilizam para uma nova versão.
Inicie o service e verifique
python app.py
Envie uma pergunta de teste:
curl -sS http://127.0.0.1:7860/api/ask \
-H 'Content-Type: application/json' \
-d '{"question":"What materials are required for travel expense reimbursement?"}'
Valide conforme os critérios a seguir:
A página abre normalmente e uma pergunta enviada retorna tanto a resposta quanto as fontes de recuperação.
Cada
[Source N]na resposta tem um chunk de política correspondente na seção de fontes abaixo.Os cabeçalhos
[Source N]carregam os rótulos de tag, comodepartment=,docType=eeffectiveDate=. Se um rótulo estiver ausente, verifique se odocumentNameretornado pela recuperação corresponde ao nome do arquivo emdocuments.jsonl.Ao perguntar sobre algo que as políticas não cobrem, a resposta declara claramente que não há disposição relevante e orienta a entrar em contato com o responsável pela política.
Após a atualização das políticas e a republicação da versão, a página recupera conteúdo da nova versão. Isso exige que a aplicação use
LATEST_PUBLISHED.
Considerações para cenários de políticas
Versões antigas de políticas — Se você mantiver versões históricas para rastreabilidade, marque o corpo com "Superseded by version X" e distinga as versões com
effectiveDate. Se a rastreabilidade não for necessária, exclua as versões antigas na página Data Management e republicue, para que o modelo não oscile entre versões.Fallback manual — Para perguntas e respostas sobre políticas críticas, mantenha um ponto de entrada de fallback manual e oriente os funcionários a seguir os documentos de política oficialmente publicados.
FAQ
A tabela a seguir lista sintomas comuns com suas causas e resoluções.
|
Symptom |
Cause and resolution |
|
Uma pergunta retorna 500 e o log mostra |
|
|
Após adicionar um filtro de tag, o número de resultados é exatamente o mesmo que sem o filtro |
Você usou um operador que não tem efeito, como |
|
Após configurar um filtro de departamento, a maioria das perguntas é respondida com "nenhuma disposição relevante" |
O ponto de entrada está limitado a um único departamento. Confirme se o escopo da pergunta corresponde a |
|
A filtragem por tag sempre retorna 0 resultados |
A ortografia do nome da tag não corresponde ao que foi gravado. A API não retorna erro, apenas 0 resultados. Verifique o nome da tag em Tags > Manage na página de detalhes da base de conhecimento. |
|
A resposta mistura versões antigas e novas da política |
Os chunks recuperados não têm informações de versão. Siga a Etapa 4 para incluir os valores das tags no contexto e marque as versões antigas como substituídas no corpo. |
|
O upload retorna |
Todos os arquivos do lote foram deduplicados por nome. Altere os nomes dos arquivos ou exclua os dados antigos primeiro. |
|
A recuperação retorna |
Nenhuma versão foi publicada ainda ou |
|
O botão Publish Version está desativado, mas a página mostra alterações pendentes |
O número de versões publicadas atingiu o limite de 3. Passe o mouse sobre o botão para ver a dica. Exclua versões antigas nos registros de versão e então publique. |
|
Você quer mostrar tags nas respostas, mas o campo |
Os resultados de recuperação não preenchem os valores das tags. Siga a Etapa 4 para consultá-los em |
|
Após adicionar tags, as definições originais de tags desaparecem |
A caixa de diálogo de gerenciamento de tags é salva como um todo. Reabra a caixa de diálogo, aguarde até que a lista termine de carregar e readicione as definições de tags ausentes. Os valores de tags já gravados nos dados não são afetados. |