tan fuerte como su base de conocimientos. Una base de conocimientos precisa y seleccionada mejora tanto la velocidad como la precisión del modelo, áreas en las que los modelos actuales a menudo se quedan cortos. De hecho, un estudio reciente muestra que los principales chatbots de IA se equivocan en casi una de cada dos consultas.
En este artículo, cubriré cómo puedes construir una base de conocimientos confiable con pasos detallados y errores a evitar.
6 pasos para construir una base de conocimientos eficaz
Adoptar un enfoque sistemático para crear una base de conocimientos le ayuda a crear una que sea estandarizada, escalable y que se explique por sí misma. Cualquier desarrollador nuevo puede agregar o actualizar fácilmente la base de conocimientos con el tiempo para mantenerla actualizada y confiable.
Para asegurarse de llegar allí, puede seguir estos seis pasos cada vez que comience a crear una base de conocimientos:
1. Recopilar datos
Un error importante a la hora de recopilar datos para una base de conocimientos es suponer que más es mejor. Te hace caer en el clásico tema de “basura entra, basura sale”.
Priorice el valor sobre el volumen y recopile todos los datos que sean relevantes para su modelo. Podría ser en forma de:
Contenido fáctico y tutorial que cubre hechos y procedimientos Contenido de resolución de problemas en forma de texto o videos instructivos Datos históricos que muestran problemas pasados o registros de ejecución Datos en tiempo real que cubren el estado del sistema en vivo o fuentes de noticias recientes Datos de dominio para que el modelo obtenga más contexto
Es importante comprender que su sistema no necesita toda la información. Por ejemplo, si está creando un chatbot de atención al cliente, es posible que su modelo solo necesite contenido fáctico y tutorial que explique las políticas y los procedimientos de la empresa. Garantiza que su modelo no invente una respuesta no válida o fuera de alcance y se ciña a lo que se le proporciona.
Consejo: existe una tendencia creciente a alimentar datos generados por IA y al mismo tiempo crear una base de conocimientos de nuevos modelos de IA. Siento que esta práctica es un arma de doble filo. Ofrece velocidad, pero debes verificar la confiabilidad y la fluidez del resultado. Optimice siempre el contenido para obtener respuestas nítidas y verifique el resultado antes de agregarlo a la base de conocimientos.
2. Limpiar y segmentar datos en trozos
Una vez que tenga listos los datos sin procesar, puede limpiarlos primero. El proceso de limpieza normalmente incluiría:
Eliminar contenido duplicado y desactualizado Eliminar detalles irrelevantes como encabezados, pies de página y números de página Estandarizar el contenido, tanto en formato como en cuanto a contenido (terminología consistente)
Estos datos limpios luego se dividen en fragmentos lógicos, donde cada fragmento contiene una idea o tema claro.
A cada fragmento también se le asignan metadatos que proporcionan un contexto rápido sobre el contenido que contiene. Estos metadatos ayudan a los modelos de IA a navegar a través de bases de conocimiento más rápido y llegar rápidamente a fragmentos que tienen detalles relevantes.
También puede configurar el acceso basado en roles en fragmentos para garantizar qué roles obtienen acceso a la información en ese fragmento. Si bien muchos roles pueden tener acceso a un modelo, no todos pueden acceder a todos los datos. La fragmentación es donde puede configurar la seguridad y el control de acceso dentro del modelo.
Consejo: una práctica recomendada que siempre sigo es fragmentar los datos en función de las consultas de los usuarios en lugar de la estructura del documento. Por ejemplo, tiene un documento sobre inicio de sesión y gestión de acceso. Puede dividirlo en preguntas comunes de los usuarios como "¿Cómo cambiar la contraseña?", "¿Cuál es la política de contraseñas?", etc. Luego puede validar estos fragmentos probándolos con consultas reales. Un conjunto seguro puede ser de 10 a 12 preguntas.
3. Organizar e indexar datos
Los fragmentos de texto se convierten en números llamados vectores utilizando un modelo de incrustación como OpenAI v3-Large, BGE-M3, etc.
Los modelos de IA pueden hojear vectores más rápido que un enorme bloque de texto. Después de la vectorización, los metadatos adjuntos al fragmento se adjuntan al vector. El fragmento final se verá así:
[ Vector (números) ] + [ Texto original ] + [ Metadatos ]
4. Elija una plataforma para almacenar datos
Puede almacenar esta salida vectorial en una base de datos vectorial como Pinecone, Milvus o Weaviate para su recuperación. Puede cargar los datos vectoriales escribiendo un código Python simple.
importar matemáticas importar tiempo importar json de clases de datos importar clase de datos, campo al escribir importar Cualquier importar numpy como np # Normalización de vectores + Metadatos def normalize_l2(vector: list[float]) -> list[float]: """ Devuelve una copia normalizada L2 de `vec`. Muchas tiendas de vectores usan similitud de producto escalar. Si normaliza los vectores a una longitud unitaria, el producto escalable se vuelve equivalente a la similitud de coseno. """ arr = np.array(vector, dtype=np.float32) norma = np.linalg.norm(arr) si norma == 0: retorno vector retorno (arr / norma).tolist() def prepare_record( doc_id: cadena, incrustación: lista[float], texto: cadena, fuente: cadena, extra_metadata: dict[cadena, Cualquiera] | Ninguno = Ninguno,) -> dict: """ Preparar un único registro para la inserción de base de datos vectorial. Los metadatos tienen dos propósitos: – Filtrado: limitar la búsqueda a un subconjunto """ metadata = { "source": source, "text_preview": text[:500], "char_count": len(text), } if extra_metadata: metadata.update(extra_metadata) return { "id": doc_id, "values": normalize_l2(embedding), "metadata": metadatos, } # Cuantización vectorial # Cuantización escalar / SQ def scalar_quantization(input_vec) -> dict: """ Esta función demuestra cómo comprimir float32 input_vec a uint8 """ input_arr = np.array(input_vec, dtype=np.float32) min, max = input_arr.min(), input_arr.max() range = (max – min) si rango == 0: cuantificado = np.zeros_like(arr, dtype=np.uint8) else: cuantificado = ((input_arr – min) / rango * 255).astype(np.uint8) return { "cuantizado": cuantificado.tolist(), "min": float(min), "max": float(max), } def scalar_dequantization(record: dict) -> list[float]: """ Puede reconstruir el vector original mediante el vector float32 aproximado de uint8. """ arr = np.array(record["quantized"], dtype=np.float32) return (arr / 255 * (record["max"] – record["min"]) + record["min"]).tolist() # Cuantización del producto / PQ def train_product_quantizer( vectores, num_subvectors: int = 8, num_centroids: int = 256, max_iterations: int = 20) -> list: """ Esta función demuestra dividir el vector en subvectores, agrupar cada uno de forma independiente """ desde sklearn.cluster import KMeans dim = vectores.shape[1]afirmar dim % num_subvectors == 0, "dim debe ser divisible por num_subvectors" sub_dim = dim // num_subvectors libros de códigos =[]para i en rango(num_subvectors): sub_vectors = vectores[:, i * sub_dim : (i + 1) * sub_dim] kmeans = KMeans(n_clusters=num_centroids, max_iter=max_iterations, n_init=1) kmeans.fit(sub_vectors) codebooks.append(kmeans.cluster_centers_) devuelve libros de códigos def pq_encode(vector: np.ndarray, libros de códigos: list[np.ndarray]) -> list[int]: """ Codifica un solo vector en códigos PQ (un uint8 por subvector) """ num_subvectors = len(codebooks) sub_dim = len(vector) // num_subvectors codes =[]para i, libro de códigos en enumerar(libros de códigos): sub_vec = vector[i * sub_dim : (i + 1) * sub_dim] distancias = np.linalg.norm(libro de códigos – sub_vec, eje=1) codes.append(int(np.argmin(distancias))) códigos de retorno def pq_decode(codes: list[int], libros de códigos: list[np.ndarray]) -> np.ndarray: """ Reconstruir el vector aproximado a partir de códigos PQ """ return np.concatenate( [libro de códigos[código] para código, libro de códigos en zip(códigos, libros de códigos)])
Consejo: Para aumentar la velocidad de carga, sugiero utilizar la opción de inserción por lotes. También puedes normalizar los vectores (hacer que todos tengan el mismo tamaño) durante la fase de carga. Después de la normalización, cuantícelo (comprima) para optimizar el almacenamiento. Este paso adicional de normalización y cuantificación acelera la recuperación posterior.
5. Optimice la recuperación
Para habilitar la recuperación de la base de datos de vectores, puede utilizar marcos de orquestación como LlamaIndex y LangChain.
LlamaIndex puede navegar a través de la base de datos vectorial más rápido y llegar al fragmento exacto donde hay contenido relacionado con la consulta del usuario.
Luego, LangChain toma datos del fragmento y los transforma según la consulta del usuario. Por ejemplo, resumir un texto o escribir un correo electrónico a partir de él.
""" Recuperación híbrida: obtenga beneficios tanto de la búsqueda de palabras clave como de la similitud de vectores. Donde cada enfoque brilla: – Palabras clave: busca coincidencias exactas, pero omitirá búsquedas con sinónimos – Incrustaciones: tiene la ventaja de capturar el significado, pero existe la posibilidad de perder la palabra clave exacta. Híbrido es una combinación de ambos para obtener lo mejor de cada uno. """ import math from collections import defaultdict from dataclasses import dataclass import numpy as np @dataclass class Documento: id: str text: str incrustación: list[float] class BestMatching25Index: def __init__(self, k1: float = 1.5, b: float = 0.75): # Aquí k1 es el límite de saturación de frecuencia del término # y b es la longitud de normalización self.k1 = k1 self.b = b self.doc_lengths: dict[str, int] = {} self.avg_doc_length: float = 0 self.doc_freqs: dict[str, int] = {} self.term_freqs: dict[str, dict[str, int]] = {} self.corpus_size: int = 0 def _tokenize(self, text: str) -> list[str]: return text.lower().split() def index(self, documentos: lista[Documento]) -> Ninguno: self.corpus_size = len(documents) para doc en documentos: tokens = self._tokenize(doc.text) self.doc_lengths[doc.id] = len(tokens) self.term_freqs[doc.id] = {} términos_vistos: set[str] = set() para token en tokens: self.term_freqs[doc.id][token] = self.term_freqs[doc.id].get(token, 0) + 1 si el token no está en términos_vistos: self.doc_freqs[token] = self.doc_freqs.get(token, 0) + 1 visto_terms.add(token) self.avg_doc_length = suma(self.doc_lengths.values()) / self.corpus_size def puntuación(self, consulta: str, doc_id: str) -> float: query_terms = self._tokenize(query) doc_len = puntuación self.doc_lengths[doc_id] = 0,0 para el término en query_terms: si el término no está en self.doc_freqs o el término no está en self.term_freqs.get(doc_id, {}): continuar tf = self.term_freqs[doc_id][term] df = self.doc_freqs[term] idf = math.log((self.corpus_size – df + 0.5) / (df + 0.5) + 1) tf_norm = (tf * (self.k1 + 1)) / ( tf + self.k1 * (1 – self.b + self.b * doc_len / self.avg_doc_length) ) puntuación += idf * tf_norm return puntuación def búsqueda(self, consulta: str, top_k: int = 10) -> lista[tuple[str, float]]: puntuaciones = [ (doc_id, self.score(query, doc_id)) para doc_id en self.doc_lengths ] puntuaciones.sort(key=lambda x: x[1], inversa=Verdadero) return puntuaciones[:top_k] clase VectorIndex: """Esta clase implementa la búsqueda inteligente usando la búsqueda híbrida. La función de índice normaliza y almacena la búsqueda de documentos implementa una búsqueda de similitud de coseno hybrid_search_weighted fusiona el índice BM25 y el índice vectorial usando el promedio ponderado Reciprocal_rank_fusion Combina los resultados de una manera eficiente """ def __init__(self): self.documents: dict[str, np.ndarray] = {} def index(self, documentos: lista[Documento]) -> Ninguno: para documentos en documentos: arr = np.array(doc.embedding, dtype=np.float32) norm = np.linalg.norm(arr) self.documents[doc.id] = arr / norm if norm > 0 else arr def search(self, query_embedding: lista[float], top_k: int = 10) -> lista[tuple[str, float]]: q = np.array(query_embedding, dtype=np.float32) q = q / np.linalg.norm(q) puntuaciones = [ (doc_id, float(np.dot(q, emb))) para doc_id, emb in self.documents.items() ] puntuaciones.sort(clave=lambda x: x[1], reverse=True) return puntuaciones[:top_k] def hybrid_search_weighted( query: str, query_embedding: list[float], bm25_index: BestMatching25Index, vector_index: VectorIndex, alpha: float = 0.5, top_k: int = 10, ) -> list[dict]: """Combina puntuaciones de palabras clave y vectores con un peso ajustable. alfa = 1.0 → búsqueda vectorial pura alfa = 0.0 → búsqueda pura de palabras clave alfa = 0.5 → peso igual (buen punto de partida) """ keyword_results = bm25_index.search(query, top_k=top_k * 2) vector_results = vector_index.search(query_embedding, top_k=top_k * 2) # Normaliza (min-max) cada lista de puntuación a [0, 1] def normalize_scores(resultados: lista[tuple[str, float]]) -> dict[str, float]: si no hay resultados: devuelve {} puntuaciones = [s para _, s en resultados] min_s, max_s = min(scores), max(scores) rng = max_s – min_s if rng == 0: devuelve {doc_id: 1.0 para doc_id, _ en resultados} return {doc_id: (s – min_s) / rng para doc_id, s en resultados} keyword_scores = normalize_scores(keyword_results) vector_scores = normalize_scores(vector_results) # Fusionar all_doc_ids = set(keyword_scores) | conjunto (puntuaciones_vectoriales) combinado =[]para doc_id en all_doc_ids: ks = keyword_scores.get(doc_id, 0.0) vs = vector_scores.get(doc_id, 0.0) combinado.append({ "id": doc_id, "score": alpha * vs + (1 – alpha) * ks, "keyword_score": ks, "vector_score": vs, }) combinado.sort(key=lambda x: x["score"], inversa=True) return combinado[:top_k] def reciprocal_rank_fusion( *ranked_lists: list[tuple[str, float]], k: int = 60, top_n: int = 10, ) -> list[dict]: """ Combina múltiples listas clasificadas, utiliza RRF (Fusión de rangos recíprocos) Puntuación RRF = suma de todas las listas de: 1 / (k + rango) ¿Por qué RRF sobre la combinación ponderada? – No se necesita normalización de puntaje (funciona en rangos, no en puntajes brutos) – No se necesita ajuste alfa – Robusto en diferentes distribuciones de puntaje – Usado por Elasticsearch, Pinecone, Weaviate bajo el capó """ rrf_scores: dict[str, float] = defaultdict(float) doc_details: dict[str, dict] = {} for list_idx, ranking_list in enumerar(ranked_lists): para rango, (doc_id, raw_score) en enumerar(ranked_list, start=1): rrf_scores[doc_id] += 1.0 / (k + rango) si doc_id no está en doc_details: doc_details[doc_id] = {} doc_details[doc_id][f"list_{list_idx}_rank"] = rango doc_details[doc_id][f"list_{list_idx}_score"] = resultados raw_score =[]para doc_id, rrf_score en rrf_scores.items(): results.append({ "id": doc_id, "rrf_score": round(rrf_score, 6), **doc_details[doc_id], }) results.sort(key=lambda x: x["rrf_score"], reverse=True) devuelve resultados[:top_n] def hybrid_search_rrf( consulta: str, query_embedding: lista[float], bm25_index: BestMatching25Index, vector_index: VectorIndex, top_k: int = 10, ) -> lista[dict]: resultados_palabra clave = bm25_index.search(consulta, top_k=top_k * 2) vector_results = vector_index.search(query_embedding, top_k=top_k * 2) devuelve reciprocal_rank_fusion(keyword_results, vector_results, top_n=top_k)
Consejo: recomiendo la recuperación híbrida basada tanto en palabras clave como en incrustaciones para una recuperación rápida. La recuperación de palabras clave es excelente para términos exactos ("Política de contraseñas"). Las incrustaciones son mejores para coincidencias conceptuales o basadas en significados. LlamaIndex es excelente en la recuperación híbrida, donde puede buscar términos exactos y contexto en torno a la pregunta.
6. Establecer una rutina de actualización y actualización automática
El último paso es asegurarse de mantener la base de conocimientos siempre actualizada. Para ello, puedes implementar el olvido selectivo. Es el proceso de sobrescribir o eliminar datos obsoletos y redundantes para mantener el modelo preciso.
¿Cómo encontrar qué datos eliminar? Existen plataformas de valoración y observabilidad para ayudar. Puede programar reglas/consultas de prueba en el marco de DeepEval que verifican periódicamente si su modelo de IA es preciso. Si las respuestas son incorrectas, la plataforma TruLens le ayuda a llegar a la parte exacta de donde se seleccionó esta respuesta.
""" Monitoreo de calidad de la base de conocimientos Estado de la base de conocimientos con la ayuda de comprobaciones automatizadas: 1. Calidad de recuperación: ¿se están encontrando los documentos correctos? 2. Detección de frescura: ¿los documentos están obsoletos o las incrustaciones están a la deriva? 3. Canalización unificada: monitoreo programado con alertas """ tiempo de importación importar json importar registro desde fecha y hora importar fecha y hora, delta de tiempo desde clases de datos importar clase de datos, campo desde escribir importar cualquiera, importar numpy invocable como np logging.basicConfig(level=logging.INFO) logger = logging.getLogger("kb_monitor") def setup_deepeval_metrics(): """Defina métricas de calidad de recuperación usando DeepEval. DeepEval proporciona métricas evaluadas por LLM: utiliza un LLM de juez para calificar si el contexto recuperado realmente ayuda a responder la pregunta. """ de deepeval.metrics import ( AnswerRelevancyMetric, FaithfulnessMetric, ContextualPrecisionMetric, ContextualRecallMetric, ) de deepeval.test_case import LLMTestCase metrics = { # ¿La respuesta aborda la pregunta? "relevancia": AnswerRelevancyMetric(threshold=0.7), # ¿La respuesta se basa en el contexto recuperado (sin alucinaciones)? "fidelidad": FaithfulnessMetric(threshold=0.7), # ¿Son realmente relevantes los documentos recuperados mejor clasificados? "context_precision": ContextualPrecisionMetric(threshold=0.7), # ¿Recuperamos todos los documentos necesarios para responder? "context_recall": ContextualRecallMetric(threshold=0.7), } return metrics, LLMTestCase def evalua_retrieval_quality( rag_pipeline: Callable, test_cases: list[dict], ) -> list[dict]: """Ejecute un conjunto de consultas de prueba a través de su canal RAG y califiquelas. Cada caso de prueba debe tener: – consulta: la pregunta del usuario – respuesta_esperada: respuesta real (para recordar/relevancia) """ de deepeval importar evaluar de deepeval.test_case importar LLMTestCase de deepeval.metrics importar ( AnswerRelevancyMetric, FaithfulnessMetric, ContextualPrecisionMetric, ContextualRecallMetric, ) resultados =[]para tc en test_cases: # Ejecute su canalización RAG real respuesta = rag_pipeline(tc["query"]) test_case = LLMTestCase( input=tc["query"], actual_output=response["answer"], expected_output=tc["expected_answer"], retrieval_context=response["retrieved_contexts"], ) metrics = [ AnswerRelevancyMetric(umbral=0.7), FaithfulnessMetric(umbral=0.7), ContextualPrecisionMetric(umbral=0.7), ContextualRecallMetric(umbral=0.7), ] para métrica en métricas: metric.measure(test_case) results.append({ "query": tc["query"], "scores": {m.__class__.__name__: m.score para m en métricas}, "aprobado": all(m.is_successful() para m en métricas), }) devuelve resultados def setup_trulens_monitoring(rag_pipeline: Callable, app_name: str = "my_kb"): """Envuelva su canal RAG con TruLens para un registro de retroalimentación continuo. TruLens registra cada consulta + respuesta + contexto recuperado, luego ejecuta funciones de retroalimentación de forma asincrónica para calificar cada una interacción """ de trulens.core import TruSession, Feedback, Select from trulens.providers.openai import OpenAI as TruLensOpenAI from trulens.apps.custom import TruCustomApp, instrument session = TruSession() # Proveedor de comentarios (utiliza un LLM para juzgar la calidad) proveedor = TruLensOpenAI() feedbacks = [ # ¿La respuesta es relevante para la consulta? Feedback(provider.relevance) .on_input() .on_output(), # ¿La respuesta se basa en el contexto recuperado? Feedback(provider.groundedness_measure_with_cot_reasons) .on(Select.RecordCalls.retrieve.rets) .on_output(), # ¿El contexto recuperado es relevante para la consulta? Feedback(provider.context_relevance) .on_input() .on(Select.RecordCalls.retrieve.rets), ] # Envuelva su canalización: cada llamada ahora se registra y califica @instrument class InstrumentedRAG: def __init__(self, pipeline): self._pipeline = pipeline @instrument def retrieve(self, query: str) -> list[str]: result = self._pipeline(consulta) devuelve resultado["contextos_recuperados"] @instrument def consulta(self, consulta: str) -> str: resultado = self._pipeline(consulta) devuelve resultado["respuesta"] instrumented = InstrumentedRAG(rag_pipeline) tru_app = TruCustomApp( instrumented, app_name=app_name, feedbacks=feedbacks, ) return tru_app, sesión def get_trulens_dashboard_url(session) -> str: """Inicie el panel de control de TruLens para visualizar la calidad a lo largo del tiempo.""" session.run_dashboard(port=8501) return "http://localhost:8501" @dataclass class DocumentFreshness: doc_id: str last_updated: datetime last_embedded: datetime source_hash: str # hash del contenido fuente en la clase de tiempo de incrustación FreshnessMonitor: """Detecta documentos obsoletos y deriva de incrustación.""" def __init__(self, staleness_threshold_days: int = 30): self.threshold = timedelta(days=staleness_threshold_days) self.freshness_records: dict[str, DocumentFreshness] = {} def Register(self, doc_id: str, source_hash: str) -> Ninguno: ahora = datetime.utcnow() self.freshness_records[doc_id] = DocumentFreshness( doc_id=doc_id, last_updated=now, last_embedded=now, source_hash=source_hash, ) def check_staleness(self) -> dict: """Buscar documentos que no se han reincrustado recientemente.""" ahora = datetime.utcnow() rancio, fresco =[],[]para doc_id, registre en self.freshness_records.items(): edad = ahora – record.last_embedded si edad > self.threshold: stale.append({"id": doc_id, "days_stale": age.days}) else: fresco.append(doc_id) return { "total": len(self.freshness_records), "fresh": len(fresh), "stale": len(stale), "stale_documents": stale, } def check_content_drift( self, doc_id: str, current_source_hash: str ) -> bool: """Compruebe si el contenido fuente cambió desde la última incorporación.""" record = self.freshness_records.get(doc_id) si no registra: devuelve True # documento desconocido, trátelo como retorno desviado record.source_hash != current_source_hash def detect_embedding_drift( old_embeddings: dict[str, list[float]], new_embeddings: dict[str, list[float]], drift_threshold: float = 0.1, ) -> dict: """Compare incrustaciones antiguas versus nuevas para los mismos documentos. Si su modelo de incrustación se actualiza (o cambia de modelo), es posible que los vectores existentes ya no sean compatibles. Esto lo detecta. """ deriva =[]common_ids = set(old_embeddings) & set(new_embeddings) para doc_id en common_ids: old = np.array(old_embeddings[doc_id]) new = np.array(new_embeddings[doc_id]) # distancia coseno: 0 = idéntico, 2 = opuesto cos_sim = np.dot(old, new) / (np.linalg.norm(antiguo) * np.linalg.norm(nuevo)) cos_dist = 1 – cos_sim if cos_dist > drift_threshold: drifted.append({ "id": doc_id, "cosine_distance": round(float(cos_dist), 4), }) return { "documents_compared": len(common_ids), "derivado": len(derivado), "drift_threshold": drift_threshold, "derifted_documents": ordenado(derivado, clave=lambda x: x["cosine_distance"], reverso=True), }
El uso de DeepEval en combinación con TruLens automatiza las pruebas periódicas de su base de conocimientos.
Principales desafíos en la construcción de una base de conocimientos (+ soluciones)
Estos son los problemas comunes que he visto con la base de conocimientos:
1. Aumento de los errores de calidad de los datos
Los modelos de IA construidos a lo largo de los años, incluso por empresas reputadas y con equipos sólidos, son alucinantes. El famoso percance del chatbot de Air Canada es un ejemplo en el que el modelo prometió un reembolso a un cliente contra una póliza que nunca existió.
Si bien todos los ingenieros intentan incluir contenido relevante en la base de conocimientos, el resultado aún presenta problemas. En mi experiencia, la falta de experiencia en el campo crea errores al identificar lo que es relevante. Quítese el sombrero técnico y póngase un límite de dominio para identificar información obsoleta, conflictiva e irrelevante en su base de conocimientos.
2. Lentitud en la recuperación
Un modelo de IA que simplemente proporcione la respuesta correcta no es suficiente. Los usuarios odian la carga o el retraso y quieren respuestas en un abrir y cerrar de ojos, al menos desde una máquina.
Los desarrolladores a menudo se atascan en la funcionalidad y no priorizan la parte de optimización, que es completamente innegociable. Utilice los siguientes consejos para resolver el problema común de lentitud:
Siga los índices HNSW (pequeño mundo navegable jerárquico) o IVF en lugar de índices planos, ya que estos agrupan temas relevantes para una recuperación rápida. Cuantice (reduzca los vectores convertidos de las consultas para que ocupen menos memoria) o divida caracteres recursivos (separe en fragmentos) de las consultas para que ocupen menos memoria. Mantenga su base de datos y su servicio de IA en la misma región de la nube para un acceso más rápido.
3. Mala escalabilidad
Para acelerar la implementación, los desarrolladores suelen tomar malas decisiones de diseño que afectan la escalabilidad a largo plazo. Uno de esos problemas es seguir una arquitectura monolítica en la que todo el almacenamiento de datos y el procesamiento de consultas se producen en un único clúster estrechamente acoplado. A medida que crece el uso del modelo, el uso de CPU/RAM aumenta en todo el clúster para cada consulta. Sugiero fragmentación horizontal (dividir datos en varios servidores pequeños) para manejar la escala de manera efectiva.
Otro problema es el costo creciente con la escala, lo que generalmente ocurre si no se cuantifican o comprimen los vectores para optimizar el almacenamiento. Los desarrolladores omiten el paso de cuantificación para llegar al modelo más rápido. La desventaja no es visible inicialmente, pero pronto la lentitud y el aumento de las facturas de la nube muestran la brecha.
Una base de conocimientos no es un volcado de datos sino un activo seleccionado
Construir una base de conocimientos no es un proyecto de una sola vez. Es un activo en evolución que necesita una optimización periódica. La estructura que cree hoy revelará lagunas mañana. Cada consulta fallida es retroalimentación y cada recuperación exitosa valida sus elecciones de diseño.
Sugiero comenzar poco a poco, seleccionar las diez preguntas más comunes para el modelo, crear documentación clara para ellas y luego probar si su modelo realmente puede brindar las respuestas correctas en el momento adecuado. Una vez que comience a obtener el resultado esperado, puede repetir el proceso para ampliar la base de conocimientos.
La diferencia entre un modelo que adivina y uno que sabe se reduce a este trabajo deliberado de curación. El refinamiento continuo hace que la siguiente búsqueda sea más fácil y los resultados más confiables.