Una aplicación típica de agente de IA en 2025 suele incluir:
- Un LLM alojado en la nube
- Una base de datos vectorial para la recuperación
- Una base de datos operativa independiente
- Herramientas de gestión de avisos y herramientas de gestión de herramientas
- Marcos de observabilidad y rastreo
- Barandillas
Cada herramienta resuelve un problema. Sin embargo, en conjunto, pueden crear una expansión arquitectónica con latencia impredecible, aumento de los costos operativos y puntos ciegos en la gobernanza. Como resultado, muchos agentes de IA nunca pasan de ser demostraciones o prototipos internos porque la complejidad aumenta demasiado rápido.
En esta publicación se explica cómo migramos una aplicación de agente de IA existente a Couchbase AI Services y al Catálogo de agentes, pasando a una única plataforma de IA lista para la producción.
El problema principal: la fragmentación mata la producción de IA
Es importante comprender por qué los sistemas agénticos tienen dificultades en la producción. La mayoría de los agentes de IA actuales se construyen a partir de demasiadas piezas poco acopladas entre sí: las indicaciones se encuentran en un sistema, los vectores en otro, las conversaciones se registran de forma inconsistente y las herramientas se invocan sin una trazabilidad clara. - lo que dificulta la depuración del comportamiento de los agentes. Al mismo tiempo, el envío de datos empresariales a terminales LLM de terceros introduce riesgos de cumplimiento normativo y seguridad. Por último, la gobernanza suele tratarse como una cuestión secundaria; muchos marcos hacen hincapié en lo que un agente puede hacer, pero no explican por qué tomó una decisión, qué indicaciones o herramientas influyeron en ella, o si esa decisión debería haberse permitido en primer lugar. Se trata de una laguna inaceptable para los flujos de trabajo empresariales reales.
¿Qué son los servicios de inteligencia artificial de Couchbase?
La creación de aplicaciones de IA a menudo implica combinar múltiples servicios: una base de datos vectorial para la memoria, un proveedor de inferencia para LLM (como OpenAI o Anthropic) y una infraestructura independiente para integrar modelos.
Servicios de inteligencia artificial de Couchbase Optimiza este proceso al proporcionar una plataforma unificada en la que conviven sus datos operativos, la búsqueda vectorial y los modelos de inteligencia artificial. Ofrece:
- API de inferencia y embeddings de LLM: Acceda a modelos LLM populares (como Llama 3) y modelos de incrustación directamente en Couchbase Capella, sin claves API externas, sin infraestructura adicional y sin salida de datos. Los datos de su aplicación permanecen dentro de Capella. Las consultas, los vectores y la inferencia de modelos se realizan donde se encuentran los datos. Esto permite experiencias de IA seguras y de baja latencia, al tiempo que se cumplen los requisitos de privacidad y cumplimiento normativo. El valor clave: datos e IA juntos, con la información confidencial guardada dentro de su sistema.
- Plataforma unificada: Mantenga su base de datos, vectorización, búsqueda y modelo en una ubicación central.
- Búsqueda vectorial integrada: Realice búsquedas semánticas directamente en sus datos JSON con una latencia de milisegundos.
¿Por qué es necesario?
A medida que pasamos de los chatbots simples a flujos de trabajo de agentes - donde los modelos de IA utilizan herramientas de forma autónoma - La latencia y la complejidad de la configuración se convierten en importantes obstáculos. Couchbase AI Services adopta un enfoque centrado en la plataforma. Al ubicar conjuntamente sus datos y servicios de IA, reduce los gastos operativos y la latencia. Además, herramientas como la Catálogo de agentes Ayuda a gestionar cientos de indicaciones y herramientas para agentes, al tiempo que proporciona funciones integradas de registro y telemetría para los agentes.
En este punto, la pregunta pasa de ser por qué es importante un enfoque centrado en la plataforma a cómo funciona en la práctica.
Veamos cómo se puede migrar una aplicación de agente existente y mejorar su rendimiento, gobernanza y confiabilidad en el proceso.
Aspecto actual de la aplicación
La aplicación actual es un agente de reclutamiento de recursos humanos diseñado para automatizar la selección inicial de candidatos. La función principal de la aplicación del agente es ingestar archivos de currículums sin procesar (PDF), comprender el contenido de los currículums utilizando un LLM y estructurar los datos no estructurados en un formato consultable enriquecido con incrustaciones semánticas en Couchbase. Permite a los profesionales de recursos humanos cargar una nueva descripción del puesto y obtener resultados de los candidatos más adecuados utilizando la búsqueda vectorial de Couchbase.
En su estado actual, la aplicación HR Sourcing es un microservicio basado en Python que envuelve un LLM con Google ADK. Conecta manualmente las definiciones de los modelos, las indicaciones de los agentes y los procesos de ejecución. Aunque es funcional, la arquitectura requiere que el desarrollador gestione el estado de la sesión en la memoria, maneje la lógica de reintentos, limpie los resultados brutos del modelo y mantenga manualmente la integración entre el LLM y la base de datos. Además, no hay telemetría integrada para nuestro agente.
La aplicación instancia manualmente un proveedor de modelos. En este caso concreto, se conecta a un modelo de código abierto alojado (Qwen 2.5-72B a través de Nebius) utilizando el LiteLLM Envoltura. La aplicación tiene que iniciar manualmente un entorno de tiempo de ejecución para el agente. Inicializa un InMemorySessionService para rastrear el estado de la conversación (aunque sea de corta duración) y un Runner para ejecutar la entrada del usuario (el texto del currículum) en la canalización del agente.
Migración de la aplicación del agente a los servicios de inteligencia artificial de Couchbase
Ahora veamos cómo migrar la lógica central de nuestro agente para utilizar los servicios de inteligencia artificial de Couchbase y el catálogo de agentes.
El nuevo agente utiliza un agente LangChain ReAct para procesar las descripciones de los puestos de trabajo, realiza una selección inteligente de candidatos mediante búsqueda vectorial y ofrece recomendaciones de candidatos clasificadas con explicaciones.
Requisitos previos
Antes de empezar, asegúrate de que tienes:
- Python 3.10+ instalado.
Instalar dependencias
Comenzaremos instalando los paquetes necesarios. Esto incluye el agentec CLI para el catálogo y los paquetes de integración LangChain.
|
1 2 3 4 5 6 |
%pip install -q \ "pydantic>=2.0.0,<3.0.0" \ "python-dotenv>=1.0.0,<2.0.0" \ "pandas>=2.0.0,<3.0.0" \ "nest-asyncio>=1.6.0,<2.0.0" \ "langchain-couchbase>=0.2.4,<0.5.0" \ "langchain-openai>=0.3.11,<0.4.0" \ "arize-phoenix>=11.37.0,<12.0.0" \ "openinference-instrumentation-langchain>=0.1.29,<0.2.0" # Install Agent Catalog %pip install agentc==1.0.0 |
Servicio de modelos centralizado (integración de servicios de modelos de IA de Couchbase)
En el original adk_resume_agent.py, teníamos que instanciar LiteLLM manualmente, gestionar claves API de proveedores específicos (Nebius, OpenAI, etc.) y manejar la lógica de conexión dentro del código de nuestra aplicación. Migraremos el código para utilizar Couchbase.
Couchbase AI Services proporciona puntos finales compatibles con OpenAI que utilizan los agentes. Para el LLM y las incrustaciones, utilizamos el paquete LangChain OpenAI, que se integra directamente con el conector LangChain Couchbase.
Habilitar servicios de IA
- Navega hasta la sección Servicios de IA de Capella en la interfaz de usuario.
- Implementa los modelos de incrustaciones y LLM.
- Para esta demostración, debe iniciar una incrustación y un LLM en la misma región que el clúster Capella donde se almacenarán los datos.
- Implementar un LLM que tenga capacidades de llamada de herramientas, como mistralai/mistral-7b-instruct-v0.3. Para las incrustaciones, puede elegir un modelo como el nvidia/llama-3.2-nv-embedqa-1b-v2.
- Anote la URL del punto final y genere claves API.
Para obtener más detalles sobre el lanzamiento de modelos de IA, puede consultar el documentación oficial.
Implementación de la lógica del código para modelos LLM y de incrustación
Necesitamos configurar los puntos finales para Capella Model Services. Capella Model Services es compatible con el formato API de OpenAI, por lo que podemos utilizar el estándar langchain-openai biblioteca apuntándola a nuestro punto final Capella. Inicializamos el modelo de incrustación con OpenAIEmbeddings y el LLM con ChatOpenAI, pero apúntalo hacia Capella.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
# Model Services Config CAPELLA_API_ENDPOINT = getpass.getpass("Capella Model Services Endpoint: ") CAPELLA_API_LLM_MODEL = "mistralai/mistral-7b-instruct-v0.3" CAPELLA_API_LLM_KEY = getpass.getpass("LLM API Key: ") CAPELLA_API_EMBEDDING_MODEL = "nvidia/llama-3.2-nv-embedqa-1b-v2" CAPELLA_API_EMBEDDINGS_KEY = getpass.getpass("Embedding API Key: ") def setup_ai_services(temperature: float = 0.0): embeddings = None llm = None if not embeddings and os.getenv("CAPELLA_API_ENDPOINT") and os.getenv("CAPELLA_API_EMBEDDINGS_KEY"): try: endpoint = os.getenv("CAPELLA_API_ENDPOINT") api_key = os.getenv("CAPELLA_API_EMBEDDINGS_KEY") model = os.getenv("CAPELLA_API_EMBEDDING_MODEL", "Snowflake/snowflake-arctic-embed-l-v2.0") api_base = endpoint if endpoint.endswith('/v1') else f"{endpoint}/v1" embeddings = OpenAIEmbeddings( model=model, api_key=api_key, base_url=api_base, check_embedding_ctx_length=False, ) except Exception as e: logger.error(f"Couchbase AI embeddings failed: {e}") if not llm and os.getenv("CAPELLA_API_ENDPOINT") and os.getenv("CAPELLA_API_LLM_KEY"): try: endpoint = os.getenv("CAPELLA_API_ENDPOINT") llm_key = os.getenv("CAPELLA_API_LLM_KEY") llm_model = os.getenv("CAPELLA_API_LLM_MODEL", "deepseek-ai/DeepSeek-R1-Distill-Llama-8B") api_base = endpoint if endpoint.endswith('/v1') else f"{endpoint}/v1" llm = ChatOpenAI( model=llm_model, base_url=api_base, api_key=llm_key, temperature=temperature, ) test_response = llm.invoke("Hello") except Exception as e: logger.error(f"Couchbase AI LLM failed: {e}") llm = None |
En lugar de codificar de forma rígida los proveedores de modelos, el agente ahora se conecta a un punto final Capella unificado, que actúa como puerta de enlace API tanto para el LLM como para el modelo de incrustación.
Desacoplamiento de mensajes y herramientas con el catálogo de agentes
El Catálogo de agentes es una potente herramienta para gestionar el ciclo de vida de las capacidades de sus agentes. En lugar de codificar de forma rígida las indicaciones y las definiciones de herramientas en sus archivos Python, puede gestionarlas como activos versionados. Puede centralizar y reutilizar sus herramientas en todos sus equipos de desarrollo. También puede examinar y supervisar las respuestas de los agentes con el Rastreador de agentes. Estas funciones proporcionan visibilidad, control y trazabilidad para el desarrollo y la implementación de agentes. Sus equipos pueden crear agentes con confianza, sabiendo que pueden ser auditados y gestionados de manera eficaz.
Sin la capacidad de rastrear el comportamiento de los agentes, resulta imposible automatizar la confianza, la validación y la corroboración continuas de las decisiones autónomas tomadas por los agentes. En el Catálogo de Agentes, esto se lleva a cabo evaluando tanto el código del agente como la transcripción de su conversación con su LLM para valorar la idoneidad de su decisión pendiente o la búsqueda en la herramienta MCP.
Así que incorporemos Agent Catalog al proyecto.
Agregar la herramienta de búsqueda vectorial
Comenzaremos añadiendo nuestra definición de herramienta para el Catálogo de agentes. En este caso, tenemos la herramienta de búsqueda vectorial.
Para agregar una nueva función de Python como herramienta para su agente, puede utilizar el comando add de la herramienta de línea de comandos Agent Catalog:
agente añadir
Si ya tiene una herramienta Python que desea agregar al catálogo de agentes, agregue agentc a sus importaciones y el decorador @agentc.catalog.tool a la definición de su herramienta. En nuestro ejemplo, definimos una función Python para realizar búsquedas vectoriales como nuestra herramienta.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
""" Vector search tool for finding candidates based on job descriptions. This tool uses Couchbase vector search to find the most relevant candidates. Updated for Agent Catalog v1.0.0 with @tool decorator. """ import os import logging from typing import List, Dict, Any from datetime import timedelta from agentc_core.tool import tool from couchbase.cluster import Cluster from couchbase.auth import PasswordAuthenticator from couchbase.options import ClusterOptions from couchbase.vector_search import VectorQuery, VectorSearch from couchbase.search import SearchRequest, MatchNoneQuery logger = logging.getLogger(__name__) def generate_embedding(text: str, embeddings_client) -> List[float]: """Generate embeddings for text using the provided embeddings client.""" try: # Use the embeddings client to generate embeddings result = embeddings_client.embed_query(text) return result except Exception as e: logger.error(f"Error generating embedding: {e}") return [0.0] * 1024 # Return zero vector as fallback @tool( name="search_candidates_vector", description="Search for candidates using vector similarity based on a job description. Returns matching candidate profiles ranked by relevance.", annotations={"category": "hr", "type": "search"} ) def search_candidates_vector( job_description: str, num_results: int = 5, embeddings_client=None, ) -> str: """ Search for candidates using vector similarity based on job description. Args: job_description: The job description text to search against num_results: Number of top candidates to return (default: 5) embeddings_client: The embeddings client for generating query embeddings Returns: Formatted string with candidate information """ try: # Get environment variables bucket_name = os.getenv("CB_BUCKET", "travel-sample") scope_name = os.getenv("CB_SCOPE", "agentc_data") collection_name = os.getenv("CB_COLLECTION", "candidates") index_name = os.getenv("CB_INDEX", "candidates_index") # Connect to Couchbase cluster = get_cluster_connection() if not cluster: return "Error: Could not connect to database" bucket = cluster.bucket(bucket_name) scope = bucket.scope(scope_name) collection = scope.collection(collection_name) # Use scope.collection(), not bucket.collection() # Generate query embedding logger.info(f"Generating embedding for job description...") if embeddings_client is None: return "Error: Embeddings client not provided" query_embedding = generate_embedding(job_description, embeddings_client) # Perform vector search logger.info(f"Performing vector search with index: {index_name}") search_req = SearchRequest.create(MatchNoneQuery()).with_vector_search( VectorSearch.from_vector_query( VectorQuery("embedding", query_embedding, num_candidates=num_results * 2) ) ) result = scope.search(index_name, search_req, timeout=timedelta(seconds=20)) rows = list(result.rows()) if not rows: return "No candidates found matching the job description." # Fetch candidate details candidates = [] for row in rows[:num_results]: try: doc = collection.get(row.id, timeout=timedelta(seconds=5)) if doc and doc.value: data = doc.value data["_id"] = row.id data["_score"] = row.score candidates.append(data) except Exception as e: logger.warning(f"Error fetching candidate {row.id}: {e}") continue # Format results if not candidates: return "No candidate details could be retrieved." result_text = f"Found {len(candidates)} matching candidates:\n\n" for i, candidate in enumerate(candidates, 1): result_text += f"**Candidate {i}: {candidate.get('name', 'Unknown')}**\n" result_text += f"- Match Score: {candidate.get('_score', 0):.4f}\n" result_text += f"- Email: {candidate.get('email', 'N/A')}\n" result_text += f"- Location: {candidate.get('location', 'N/A')}\n" result_text += f"- Years of Experience: {candidate.get('years_experience', 0)}\n" skills = candidate.get('skills', []) if skills: result_text += f"- Skills: {', '.join(skills[:10])}\n" technical_skills = candidate.get('technical_skills', []) if technical_skills: result_text += f"- Technical Skills: {', '.join(technical_skills[:10])}\n" summary = candidate.get('summary', '') if summary: # Truncate summary if too long summary_text = summary[:200] + "..." if len(summary) > 200 else summary result_text += f"- Summary: {summary_text}\n" result_text += "\n" return result_text except Exception as e: logger.error(f"Error in vector search: {e}") import traceback traceback.print_exc() return f"Error performing candidate search: {str(e)}" |
Añadir las indicaciones
En la arquitectura original, las instrucciones del agente estaban ocultas dentro del código Python como variables de cadena grandes, lo que dificultaba su versión o actualización sin una implementación completa. Con el Catálogo de agentes, ahora definimos nuestra persona “Reclutador de RR. HH.” como un activo independiente y gestionado mediante indicaciones. Utilizando una definición YAML estructurada (record_kind: prompt), creamos el hr_recruiter_assistant. Esta definición no solo contiene el texto, sino que encapsula todo el comportamiento del agente, definiendo estrictamente el patrón ReAct (Pensamiento → Acción → Observación) que guía al LLM para utilizar la herramienta de búsqueda vectorial de forma eficaz.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
record_kind: prompt name: hr_recruiter_assistant description: AI-powered HR recruiter assistant that helps match candidates to job descriptions using vector search annotations: category: hr type: recruitment content: | You are an expert HR recruiter assistant with deep knowledge of talent acquisition and candidate matching. Your role is to help HR professionals find the best candidates for job openings by analyzing job descriptions and searching through a database of candidate profiles. You have access to the following tools: {tools} Use the following format for your responses: Question: the input question or job description you must analyze Thought: think about what information you need to find the best candidates Action: the action to take, should be one of [{tool_names}] Action Input: the input to the action (for candidate search, provide the job description text) Observation: the result of the action ... (this Thought/Action/Action Input/Observation can repeat N times) Thought: I now have enough information to provide recommendations Final Answer: Provide a comprehensive summary of the top candidates including: - Candidate names and key qualifications - Skills match percentage and relevance - Years of experience - Why each candidate is a good fit for the role - Any notable strengths or unique qualifications IMPORTANT GUIDELINES: - Always use the search_candidates_vector tool to find candidates - Analyze the job description to understand required skills and experience - Provide detailed reasoning for candidate recommendations - Highlight both technical skills and soft skills when relevant - Be specific about match percentages and scores - Format your final answer in a clear, professional manner Begin! Question: {input} Thought: {agent_scratchpad} |
Indexación y publicación de los archivos locales
Utilizamos agentec para indexar nuestros archivos locales y publicarlos en Couchbase. Esto almacena los metadatos en la base de datos, lo que permite que el agente los busque y los encuentre en tiempo de ejecución.
|
1 2 3 4 5 |
# Create local index of tools and prompts agentc index . # Upload to Couchbase agentc publish |
En nuestro código, inicializamos el Catálogo y utilizamos catalog.find() para recuperar las indicaciones y herramientas verificadas. Ya no codificamos las indicaciones de forma rígida, sino que las obtenemos.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# BEFORE: Hardcoded Prompt Strings # parse_instruction = "You are a resume parsing assistant..." import agentc from agentc import Catalog, Span # AFTER: Dynamic Asset Loading catalog = Catalog() # Load the "search" tool dynamically tool_result = catalog.find("tool", name="search_candidates_vector") # Load the "recruiter" persona dynamically prompt_result = catalog.find("prompt", name="hr_recruiter_assistant") # We act on the retrieved metadata tools = [Tool(name=tool_result.meta.name, func=...)] |
Motor de razonamiento estandarizado (integración LangChain)
La aplicación anterior utilizaba un canal SequentialAgent personalizado. Aunque era flexible, esto significaba que teníamos que mantener nuestros propios bucles de ejecución, gestión de errores y lógica de reintento para los pasos de razonamiento del agente.
Aprovechando la compatibilidad del Catálogo de Agentes con LangChain, cambiamos a una arquitectura de agente ReAct (Reason + Act) estándar. Simplemente introducimos las herramientas y las indicaciones obtenidas del catálogo directamente en create_react_agent.
¿Cuál es la ventaja? Obtenemos bucles de razonamiento estándar en la industria. - Pensamiento -> Acción -> Observación - listo para usar. El agente ahora puede decidir de forma autónoma buscar “desarrolladores React”, analizar los resultados y, a continuación, realizar una segunda búsqueda de “ingenieros frontend” si la primera arroja pocos resultados. Algo con lo que la canalización lineal ADK tenía dificultades.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
def create_langchain_agent(self, catalog: Catalog, embeddings, llm): try: # Load tools from catalog using v1.0.0 API tool_result = catalog.find("tool", name="search_candidates_vector") # Create tool wrapper that injects embeddings client def search_with_embeddings(job_description: str) -> str: return tool_result.func( job_description=job_description, num_results=5, embeddings_client=embeddings, ) tools = [ Tool( name=tool_result.meta.name, description=tool_result.meta.description, func=search_with_embeddings, ), ] # Load prompt from catalog using v1.0.0 API prompt_result = catalog.find("prompt", name="hr_recruiter_assistant") if prompt_result is None: raise ValueError("Could not find hr_recruiter_assistant prompt in catalog. Run 'agentc index' first.") custom_prompt = PromptTemplate( template=prompt_result.content.strip(), input_variables=["input", "agent_scratchpad"], partial_variables={ "tools": "\n".join([f"{tool.name}: {tool.description}" for tool in tools]), "tool_names": ", ".join([tool.name for tool in tools]), }, ) # Create agent agent = create_react_agent(llm, tools, custom_prompt) agent_executor = AgentExecutor( agent=agent, tools=tools, verbose=True, handle_parsing_errors=handle_parsing_error, max_iterations=5, max_execution_time=120, early_stopping_method="force", return_intermediate_steps=True, ) logger.info("LangChain ReAct agent created successfully") return agent_executor |
Observabilidad integrada (rastreo de agentes)
En la aplicación anterior del agente, la observabilidad se limitaba a las instrucciones print(). No había forma de “reproducir” la sesión de un agente para comprender por qué rechazaba a un candidato específico.
Agent Catalog proporciona rastreo. Permite a los usuarios utilizar SQL++ con rastreos, aprovechar el rendimiento de Couchbase y obtener información detallada sobre las indicaciones y herramientas en la misma plataforma.
Podemos añadir observabilidad transaccional utilizando catalog.Span(). Envolvemos la lógica de ejecución en un administrador de contexto que registra cada pensamiento, acción y resultado en Couchbase. Ahora podemos ver un “rastreo” completo de la sesión de reclutamiento en la interfaz de usuario de Capella, que muestra exactamente cómo el LLM procesó el currículum de un candidato.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
application_span = catalog.Span(name="HR Recruiter Agent") # AFTER: granular observability with span.new(name="job_matching_query") as query_span: # Log the input query_span.log(UserContent(value=job_description)) # Run the agent response = agent.invoke({"input": job_description}) # Log the agent's final decision query_span.log(AssistantContent(value=response["output"])) |

Conclusión
Los agentes de IA fallan en la producción no porque los LLM carezcan de capacidad, sino porque los sistemas agenticos pueden llegar a ser demasiado complejos. Al adoptar un enfoque centrado en la plataforma con Couchbase AI Services y el Catálogo de Agentes, transformamos un agente complejo en un sistema agentico gobernado y escalable.
Si hoy en día estás creando agentes de IA, la verdadera pregunta no es ¿Qué LLM utilizar? - Así es como ejecutará los agentes de forma segura, observable y a gran escala. Los servicios de IA de Couchbase están diseñados precisamente para eso.