LlamaIndex ‘legal-kb’: Recuperación agente sobre Index v2 con herramientas de recuperación, búsqueda, lectura y grep

LlamaIndex ha publicado legal-kb, una aplicación de referencia pública en GitHub. Se describe como una base de conocimiento para documentos legales, impulsada por LlamaIndex Index v2 (la plataforma LlamaParse). El proyecto demuestra un patrón que el equipo llama Arnés de recuperación para la recuperación agencial.

El enfoque difiere de la recuperación de un solo disparo. En lugar de una búsqueda integrada por consulta, un agente recibe herramientas de estilo sistema de archivos. Luego puede rastrear una gran base de conocimientos en evolución para resolver una tarea. Las herramientas reflejan las que los ingenieros de operaciones ya conocen: búsqueda semántica y de palabras clave, grep de expresiones regulares, búsqueda de archivos y lectura.

legal-kb es una aplicación web TanStack Start que funciona, no una biblioteca. Usted inicia sesión, crea un proyecto, carga archivos y chatea con un agente. Cada proyecto se refleja como un LlamaCloud Index v2 administrado. Los archivos cargados se analizan e indexan automáticamente en segundo plano. Luego, el agente de chat consulta ese índice en vivo durante cada turno.

El arnés de recuperación, en términos sencillos

El arnés proporciona una canalización de datos persistente sobre sus documentos. Se conecta a una fuente de datos, la indexa y la mantiene actualizada. Además de esa canalización, expone un conjunto de herramientas al agente.

Esas herramientas están deliberadamente cerca de las operaciones del sistema de archivos. Un agente puede enumerar archivos, leer un archivo, buscar dentro de un archivo o ejecutar una búsqueda híbrida. Debido a que las herramientas son genéricas, puede conectar el arnés a sus propios agentes.

El agente en src/lib/agent.ts recibe cuatro herramientas. Cada uno se asigna a una API de recuperación de Index v2. La siguiente tabla los enumera tal como están implementados.

HerramientaAPI de respaldoParámetros claveQué haceretrievebeta.retrieval.retrievequery, top_k, score_threshold, rerank_top_n, file_name, file_versionEjecuta búsqueda semántica híbrida; reclasificación opcional; devuelve fragmentos más citasfindFilesbeta.retrieval.findfile_name, file_name_containsBusca archivos por nombre exacto o subcadena; pagina automáticamentereadFilebeta.retrieval.readfile_id, offset, max_lengthLee el contenido del archivo sin formato, con ventanas de desplazamiento y longitudgrepFilebeta.retrieval.grepfile_id, patrón, context_chars, limitCoincide con un patrón en un archivo; devuelve posiciones de caracteres

El indicador del sistema impone una orden. El agente debe llamar primero a findFiles para establecer el inventario de documentos. Luego se reduce con recuperar y confirma la redacción exacta con readFile o grepFile antes de citar.

Cómo funciona debajo del capó

Las cargas siguen un proceso claro en src/lib/files.ts. Los bytes se envían al directorio fuente de LlamaCloud del proyecto. Una fila File y ProjectFile se escriben en PostgreSQL a través de Prisma. Se activa una sincronización de índice pero no se espera; el estado de las encuestas de UI hasta que esté listo.

El control de versiones tiene como ámbito el par (proyecto, nombre de archivo). Volver a cargar nda.pdf en el mismo proyecto produce v1, v2, v3 uno al lado del otro. La capa de recuperación filtra en el campo de metadatos de la versión. Esto proporciona control de versiones sobre la propia base de conocimientos.

El agente utiliza ToolLoopAgent de Vercel AI SDK 6. Eliges OpenAI o Anthropic por turno y traes tus propias claves. El razonamiento es fluido: los modelos de Claude utilizan el pensamiento extendido; Los modelos de razonamiento OpenAI utilizan un esfuerzo de razonamiento medio.

A continuación se ofrece una vista condensada pero fiel de la herramienta de recuperación y el agente.

importar { LlamaCloud } de ‘@llamaindex/llama-cloud’ importar { herramienta, ToolLoopAgent } de ‘ai’ importar { z } de ‘zod’ importar { makeCitationId } de ‘./citations’ // Un cierre de herramienta por índice. API de recuperación Wraps Index v2. function createLlamaParseTools(apiKey: cadena, projectId: cadena, indexId: cadena) { const client = new LlamaCloud({ apiKey }) const retrieve = herramienta({ descripción: ‘Ejecutar una consulta de recuperación semántica contra un índice.’, inputSchema: z.object({ consulta: z.string(), top_k: z.number().nullable(), score_threshold: z.number().nullable(), rerank_top_n: z.number().nullable(), // configurado para habilitar la reclasificación file_name: z.string().nullable(), // filtro de metadatos file_version: z.number().nullable(), }), ejecutar: async ({ query, top_k, score_threshold, rerank_top_n, file_name }) => { const custom_filters = file_name ? nombre_archivo: { operador: ‘eq’ como constante, valor: nombre_archivo } }: respuesta constante indefinida = esperar cliente.beta.retrieval.retrieve({ index_id: indexId, proyecto_id: proyectoId, consulta, top_k, puntaje_umbral, reclasificar: rerank_top_n! = nulo? {habilitado: verdadero, top_n: rerank_top_n}: indefinido, filtros personalizados, }) // Devuelve una lista legible por el modelo más citas que controlan los chips de UI. })) const formateado = respuesta.resultados .map((r, i) => `### Resultado #${i + 1}\n\n${r.content.slice(0, 600)}`) .join(‘\n\n—\n\n’) return { formateado, citas } }, }) // findFiles / readFile / grepFile siguen la misma forma, respaldados por // client.beta.retrieval.find / .read / .grep return { recuperar /* , findFiles, readFile, grepFile */ } } función de exportación buildAgent(modelo, apiKey: string, projectId: string, indexId: string) { return new ToolLoopAgent({ modelo, herramientas: createLlamaParseTools(apiKey, projectId, indexId), instrucciones: ‘Llame siempre a findFiles primero, tierra cada respuesta en los documentos, ‘ + ‘y citar los identificadores en línea como `cite:`.’, }) }

Las respuestas llevan citas visuales. Cada fragmento recuperado obtiene una identificación corta, como cite:c7f2qa. El agente hace referencia a esa identificación en línea y la interfaz de usuario muestra un chip de cita en el que se puede hacer clic. Al hacer clic en él, se abre la captura de pantalla de la página de origen con rectángulos de cuadro delimitador sobre el texto citado.

El RAG ingenuo frente al arnés de recuperación agente

El arnés es un modelo de ejecución diferente al RAG de un solo disparo. La siguiente comparación se centra en el comportamiento.

DimensiónRagagentic Arnés de recuperación ingenuo/de un solo disparo (índice v2)Flujo de recuperaciónBúsqueda de un vector por consultaBucle de herramienta de varios pasos: buscar → recuperar → leer/grepModos de búsquedaSolo similitud de vectoresBúsqueda semántica híbrida, palabra clave y expresión regular grepContextoPartes top-k fijasEl agente lee archivos completos o ventanas a pedidoFrescuraÍndice estáticoCanalización persistente con sincronización y control de versionesControl de precisiónMayormente ocultotop_k, puntaje_umbral, rerank_top_n expuestoCitasIds de trozosCitas visuales con capturas de pantalla de páginas y cuadros bMejor ajusteRespuestas a preguntas cortasTareas de documentos de largo horizonte

Casos de uso, con ejemplos.

El diseño apunta a dominios donde los agentes navegan por grandes conjuntos de documentos. Legal y fintech son los ejemplos indicados.

Considere una pregunta sobre el contrato: “¿Qué aviso se necesita para rescindir el MSA?” El agente enumera los archivos, ejecuta la recuperación y luego busca la cláusula exacta. Responde con una cita a la página específica. Considere la debida diligencia en una sala de datos: un agente puede buscar archivos por nombre y luego leer el archivo de cada candidato. Verifica cláusulas sin que un humano abra cada PDF. Considere una base de políticas versionada: debido a que la recuperación acepta un filtro file_version, un agente puede consultar una versión específica. Esto admite el seguimiento de cambios a lo largo del tiempo.

Implementación de referencia

/g,’>’);} función match(texto){ var t=text.toLowerCase(),best=null,hit=0; INTENTS.forEach(function(it){ var c=0; it.kw.forEach(function(k){ if(t.indexOf(k)>-1)c++; }); if(c>hit){hit=c;best=it;} }); devolver mejor; } función litFile(fn){ root.querySelectorAll(‘.file’).forEach(function(f){ f.classList.toggle(‘lit’, f.getAttribute(‘data-fn’)===fn); }); } función addStep(cls,label,html,delay){ return new Promise(function(res){ setTimeout(function(){ var s=document.createElement(‘div’);s.className=”step”; s.innerHTML=’

‘+etiqueta+’

‘+html; feed.appendChild(s); silbido(); res(); },demora); }); }var C1,C2; función ejecutar(forceKey){ si(ocupado)retorno; ocupado=verdadero; ir.disabled=verdadero; if(empty)empty.style.display=’none’; feed.innerHTML=”; var it = forceKey? INTENTS.filter(función(x){return x.key===forceKey;})[0] : coincidencia(entrada.valor||”); C1=deshacerse(); C2=deshacerse(); if(!it){ addStep(‘find’,’findFiles’,callHTML(‘findFiles’,{},’3 archivos: Mutual_NDA.pdf (v2), MSA_Acme_Vendor.pdf (v1), Empleo_Agreement.pdf (v1)’),150) .then(function(){ return addStep(‘ans’,’answer’,’

Los documentos indexados no contienen suficiente información para responder a eso. Pruebe la rescisión, la confidencialidad, las condiciones de pago, la no competencia, la responsabilidad o la ley aplicable.

‘,700); }) .entonces(hecho); devolver; } litFile(it.archivo); // 1) findFiles (siempre primero) addStep(‘find’,’findFiles’,callHTML(‘findFiles’,{},’3 archivos listados · ‘+it.file+’ (v’+it.ver+’) es un candidato’),150) // 2) recovery (búsqueda híbrida) .then(function(){ return addStep(”,’retrieve’,callHTML(‘retrieve’,{query:it.query,top_k:5,rerank_top_n:3},null),820); .then(function(){ return addStep(”,’results’,retrieveResults(it),780); }) // 3) grep para confirmar la redacción exacta .then(function(){ return addStep(‘grep’,’grepFile’,callHTML(‘grepFile’,{file:it.file,pattern:it.grep.slice(0,32)+’…’},’1 coincidencia confirmada en p.’+it.page),820); }) // 4) respuesta fundamentada con citas .then(function(){ return addStep(‘ans’,’answer’,’

‘+respuestaHTML(es)+’

‘,780); }) .entonces(hecho); } función hecha(){ ocupada=falso; ir.disabled=false; } llamada de funciónHTML(nombre,argumentos,nota){ var a=Object.keys(args).map(función(k){ var v=args[k]; var val = tipo de v===’número’? ‘‘+v+’‘ : ‘“‘+esc(Cadena(v))+'”‘; devolver ‘‘+k+’: ‘+valor; }).unirse(‘, ‘); línea var=”

→ herramienta “+nombre+'({ ‘+un+’ })’; if(nota) línea+=’
✓ ‘+esc(nota)+’‘; línea+=’

‘; línea de retorno; } función recuperarResultados(it){ var s2=(it.score-0.14).toFixed(3); varh=”

“+’

Resultado #1 · ‘+it.file+’ · p.’+it.page+’puntuación ‘+it.score.toFixed(3)+’ · citar:’+C1+’

‘+esc(it.chunk.slice(0,150))+’…

‘+ ‘

Resultado #2 · ‘+it.file+’ · p.’+it.page+’puntuación ‘+s2+’ · citar:’+C2+’

‘+esc(it.chunk.slice(120,250))+’…

‘+ ‘

‘; devolver h; } función respuestaHTML(it){ var html=esc(it.answer) .replace(‘§CITE§’,’citar:’+C1+’‘) .replace(‘§CITE2§’,’citar:’+C2+’‘); // reserva para modal root._cur=it; devolver html; } // cita modal var modal=root.querySelector(‘#modal’), shot=root.querySelector(‘#shot’), mpv=root.querySelector(‘#mpv’), mt=root.querySelector(‘#mt’); feed.addEventListener(‘click’,function(e){ var chip=e.target.closest(‘.citechip’); if(!chip)return; var it=root._cur; if(!it)return; mt.textContent=it.file+’ · página ‘+it.page+’ · v’+it.ver; shot.innerHTML=’

‘+esc(es.fragmento)+’

‘+ ”; mpv.textContent=it.chunk; modal.classList.add(‘activado’); silbido(); }); root.querySelector(‘#mx’).onclick=function(){modal.classList.remove(‘on’);ping();}; modal.onclick=function(e){ if(e.target===modal){modal.classList.remove(‘on’);ping();} }; go.onclick=función(){ ejecutar(nulo); }; input.addEventListener(‘keydown’,function(e){ if(e.key===’Enter’)run(null); }); // cambio de tamaño automático para la función de inserción de WordPress ping(){ try{ var h=document.getElementById(‘mtp-harness’).offsetHeight+40; parent.postMessage({tipo:’mtp-harness-height’,height:h},’*’); }catch(e){} } window.addEventListener(‘cargar’,ping); window.addEventListener(‘cambiar tamaño’,ping); setTimeout(ping,300); })();