Skip to content

Tutorial: rag con Ollama, en local y con citas

Actualizado:

Tienes Ollama corriendo en tu máquina con qwen2.5:7b. Responde rápido, no paga API y no manda nada a la nube. Pero le preguntas algo de tu producto y se lo inventa con toda la seguridad del mundo. Eso no es culpa del modelo: nunca leyó tus documentos. Rag (retrieval-augmented generation) es la forma de dárselos. En este tutorial vas a hacer rag con Ollama en local, con citas, sin escribir un cargador de documentos y sin levantar una base de datos vectorial aparte.

Construimos SynapCores, la base de datos que usamos en este tutorial. Los comandos y las salidas corrieron en SynapCores 2.4.8 Community Edition (build 575). El producto y los documentos de ejemplo son inventados.

El problema, con un ejemplo

Inventamos un router, el Orbit X2, y le escribimos cuatro documentos de soporte en Markdown. En el de códigos de error hay esta línea:

text
- **E-512**: Firmware signature mismatch. Do NOT power off. Wait 10 minutes; the router rolls back automatically. If the light stays red, hold the reset button for 30 seconds.

Traducido: la firma del firmware no coincide, no lo apagues, espera 10 minutos a que vuelva solo a la versión anterior y, si la luz sigue roja, mantén presionado el botón de reset 30 segundos.

Ahora pregúntale al modelo, sin documentos: "Mi router Orbit X2 muestra el error E-512. ¿Qué significa? ¿Lo desconecto?".

text
Error code E-512 on your Orbit X2 router typically indicates an issue with the power supply or the connection to the power source. Here are some steps you can take:
...
3. **Reset Router**: Unplug the router from the power source for about 10 seconds and then plug it back in. ...
...
Unplugging the router is generally safe as long as you follow proper procedures ...

Adivina un problema de corriente y te dice que lo desconectes. El documento dice justo lo contrario. Si ese texto lo lee un cliente, el router se queda a medio actualizar.

El rag de siempre: cuatro piezas

Casi todos los tutoriales de rag con Ollama arman lo mismo:

  1. Un cargador que lee los archivos y los divide en fragmentos.
  2. Un modelo de embeddings que convierte cada fragmento en un vector.
  3. Una base de datos vectorial donde guardar esos vectores.
  4. El código que busca los fragmentos, arma el prompt y llama al modelo.

Funciona. Pero son cuatro piezas, cada una con su configuración, más el código que las une. Y cuando alguien edita un documento, tienes que acordarte de volver a correr la ingesta.

La idea: el servidor que responde el chat también guarda tus documentos

SynapCores es una base de datos en un solo binario: SQL, vectores, grafo y el modelo de lenguaje corren en el mismo proceso. Además implementa la API HTTP de Ollama (/api/chat, /api/tags, /api/embed). Los modelos tienen los mismos nombres que en Ollama.

Para tu código, es un servidor de Ollama con dos diferencias:

  • Cada petición lleva una clave de API (Authorization: Bearer ...). Puede ser una clave de solo lectura.
  • Tus documentos viven en la base de datos. Cuando una llamada a /api/chat trae la opción rag, el servidor busca primero en tus documentos, le pasa los fragmentos al modelo y te devuelve las fuentes en los encabezados de la respuesta.
text
ollama.Client(host=SynapCores)
        │   /api/chat + options["rag"]
        ▼
SynapCores: busca en tu carpeta ──► qwen2.5:7b ──► respuesta + fuentes en los encabezados

Eso es lo que no te da un servidor de Ollama solo: la búsqueda, los embeddings y el modelo están en el mismo lugar, así que el rag es una opción de la llamada y no un proyecto aparte.

No necesitas el servidor de Ollama instalado. Lo que conservas es el cliente: la librería ollama de Python, sin cambios.

Lo que vas a usar

Base de datos SynapCores 2.4.8 Community Edition, en http://localhost:8080
Modelo de chat qwen2.5:7b
Modelo de embeddings all-minilm:latest
Cliente librería ollama de Python (probamos la 0.6.3)
Máquina de prueba Apple M4, 24 GB de memoria unificada

Paso 1: instala SynapCores y elige los modelos

Descarga la Community Edition desde synapcores.com/download y sigue las instrucciones de tu sistema. Es gratis.

Abre el archivo de configuración ~/.synapcores/gateway.toml, busca la sección [query.ai_service] y deja estas líneas:

toml
[query.ai_service]
provider        = "local"
model           = "qwen2.5:7b"
embedding_model = "all-minilm:latest"
auto_pull       = true

Con auto_pull = true, SynapCores descarga los dos modelos la primera vez que arranca. Corren en tu máquina, dentro del proceso de la base de datos.

Arranca la base de datos. En el primer arranque aparece un recuadro FIRST-BOOT CREDENTIALS con el usuario admin, su contraseña y una clave de API, en la línea OpenClaw Memory API key. Copia esa clave: empieza con aidb_, tiene acceso completo (FullAccess) y no vence. El recuadro se muestra una sola vez.

Paso 2: una clave de solo lectura para tu app

La clave de administrador sirve para configurar. Tu app usa una de solo lectura:

bash
export SC_URL=http://localhost:8080
export ADMIN_KEY=aidb_...        # la clave del primer arranque, solo para configurar

curl -s $SC_URL/v1/api-keys \
  -H "Authorization: Bearer $ADMIN_KEY" -H 'content-type: application/json' \
  -d '{"name": "orbit-app", "permission": "ReadOnly", "expires_in_days": 14}'
json
{"api_key":{"id":"<key-id>","roles":null,"databases":null,"name":"orbit-app","permission":"ReadOnly","table_grants":null,"key":"aidb_...","key_preview":"...xxxx","last_used":null,"expires_at":"2026-11-25T14:10:43.370724Z","is_active":true,"created_at":"2026-11-11T14:10:43.370724Z"},"raw_key":"aidb_...","warning":"This API key will only be shown once. Please save it securely."}

Guarda el valor de raw_key en la variable de entorno SC_KEY. Se muestra una sola vez. El código de Python la lee de ahí, así no queda escrita en el archivo.

Un detalle: una clave nueva no puede durar más que la clave que la crea (si lo intentas, la respuesta es un 403 credential_authority_exceeded). La clave del primer arranque no vence, así que con ella no hay problema.

Paso 3: registra tu carpeta de documentos

Crea una carpeta y baja los cuatro documentos de ejemplo:

bash
mkdir -p ~/rag-ollama/docs && cd ~/rag-ollama
B=https://synapcores.com/demos/two-stacks
for f in returns-and-refunds shipping troubleshooting-error-codes warranty; do
  curl -fsSL -o docs/$f.md $B/docs/$f.md
done

Ahora dile a SynapCores que esa carpeta es una colección:

bash
curl -s $SC_URL/v1/filesystem-collections \
  -H "Authorization: Bearer $ADMIN_KEY" -H 'content-type: application/json' \
  -d "{\"name\": \"orbit_support\", \"filesystem_path\": \"$HOME/rag-ollama/docs\", \"file_types\": [\"md\"], \"watch\": true}"

# unos segundos después, revisa el estado
curl -s $SC_URL/v1/filesystem-collections -H "Authorization: Bearer $ADMIN_KEY"
json
{"data":[{"id":"<collection-id>","name":"orbit_support","tenant_id":"<tenant_id>","owner_user_id":"<owner_user_id>","created_by":"admin","created_at":"2026-11-11T14:10:58.457988Z","modified_at":"2026-11-11T14:10:58.715704Z","filesystem_path":"/Users/tu-usuario/rag-ollama/docs","watch_enabled":true,"recursive":true,"file_types":["md"],"total_files":4,"total_chunks":4,"status":"active","vector_space":"fs__<tenant_id>__<collection-id>","embedding_model":"all-minilm:latest","chunk_size":512,"chunk_overlap":64}],"meta":{...}}

Lo que importa de esa respuesta:

  • total_files y total_chunks en 4: los cuatro archivos ya están indexados.
  • file_types: la extensión, sin punto.
  • watch_enabled: true: la base de datos vigila la carpeta y vuelve a indexar lo que cambie. Lo vas a probar en el paso 6.
  • embedding_model: el modelo que convierte cada fragmento en vector. Por eso la búsqueda es por significado y no por palabras exactas.

Esta llamada reemplaza al cargador, al modelo de embeddings y a la base de datos vectorial del rag de siempre.

Paso 4: conecta el cliente de Ollama

Instala el cliente oficial (pip install ollama) y crea check.py:

python
# check.py
import os
import ollama

client = ollama.Client(
    host=os.environ.get("SC_URL", "http://localhost:8080"),
    headers={"Authorization": "Bearer " + os.environ["SC_KEY"]},
)
for m in client.list().models:
    print(m.model)
text
all-minilm:latest
qwen2.5:7b

Esto es /api/tags de Ollama, el mismo que ya usas. La lista muestra los modelos que tenga el servidor.

Paso 5: la misma pregunta, con y sin rag

Crea ask.py. Hace cada pregunta dos veces: una como la hace tu app hoy y otra con una opción rag dentro de options.

python
# ask.py
import json, os
import ollama

seen = {}
def keep_headers(resp):                       # las fuentes vienen en los encabezados
    seen.clear()
    seen.update({k: v for k, v in resp.headers.items() if k.lower().startswith("x-synapcores")})

client = ollama.Client(
    host=os.environ.get("SC_URL", "http://localhost:8080"),
    headers={"Authorization": "Bearer " + os.environ["SC_KEY"]},
    event_hooks={"response": [keep_headers]},
)

QUESTIONS = [
    "My Orbit X2 router shows error E-512. What does it mean and should I unplug it?",
    "How many years of warranty does the Orbit X2 Pro router have?",
]
OPTS = {"temperature": 0, "seed": 7}
RAG = {"source": "filesystem:orbit_support", "top_k": 2}

for q in QUESTIONS:
    for label, options in (("sin rag", OPTS), ("con rag", {**OPTS, "rag": RAG})):
        r = client.chat(model="qwen2.5:7b",
                        messages=[{"role": "user", "content": q}],
                        options=options)
        print(f"\n--- {label}: {q}")
        print(r.message.content.strip())
        print("  grounding:", seen.get("x-synapcores-grounding", "(ninguno)"))
        for src in json.loads(seen.get("x-synapcores-citations") or "[]"):
            print("  source:   ", src)

temperature: 0 y una seed fija hacen que dos corridas den el mismo texto. Corre python ask.py:

text
--- sin rag: My Orbit X2 router shows error E-512. What does it mean and should I unplug it?
Error code E-512 on your Orbit X2 router typically indicates an issue with the power supply or the connection to the power source. Here are some steps you can take:
...
  grounding: (ninguno)

--- con rag: My Orbit X2 router shows error E-512. What does it mean and should I unplug it?
Error E-512 on your Orbit X2 router indicates a firmware signature mismatch. This issue typically means there's a problem with the software running on your router.

According to the troubleshooting guide, you should not power off the device immediately. Instead, the recommendation is to wait for 10 minutes in hopes that the router will resolve this issue automatically. If after waiting, the red light persists and remains unresponsive, then you can proceed by holding down the reset button for 30 seconds.

However, before taking any action, it's a good idea to contact Orbit Networks support or your local service provider for further assistance, as they might have more specific guidance based on your particular situation.
  grounding: grounded
  source:    filesystem:orbit_support/troubleshooting-error-codes.md#0
  source:    filesystem:orbit_support/warranty.md#0

--- sin rag: How many years of warranty does the Orbit X2 Pro router have?
The specific warranty period for the Orbit X2 Pro router can vary depending on the manufacturer's policies and where you purchased it from. Typically, consumer routers come with a one-year warranty as standard. ...
  grounding: (ninguno)

--- con rag: How many years of warranty does the Orbit X2 Pro router have?
The Orbit X2 Pro router comes with a 5-year warranty.
  grounding: grounded
  source:    filesystem:orbit_support/warranty.md#0
  source:    filesystem:orbit_support/returns-and-refunds.md#0

Corre el script otra vez: el texto sale igual, palabra por palabra.

Compara:

  • E-512. Sin rag, adivina un problema de corriente y sugiere desconectar. Con rag: firmware signature mismatch, you should not power off the device immediately, wait for 10 minutes y, si la luz sigue roja, holding down the reset button for 30 seconds. Fuente: troubleshooting-error-codes.md.
  • Garantía. Sin rag: Typically, consumer routers come with a one-year warranty. Con rag: The Orbit X2 Pro router comes with a 5-year warranty. Fuente: warranty.md.

Fíjate en una cosa: el modelo escribe "should not power off the device immediately". El documento dice "Do NOT power off", sin matices. El rag le dio el dato correcto, pero el modelo lo suavizó. Por eso conviene mostrar la fuente junto a la respuesta.

En tu app cambiaron dos cosas:

  1. El host del cliente apunta a SynapCores y lleva la clave.
  2. options tiene una entrada más: "rag": {"source": "filesystem:orbit_support", "top_k": 2}. top_k: 2 mete en el prompt los dos fragmentos más parecidos a la pregunta.

No escribiste cargador, ni divisor de texto, ni conexión a una base de datos vectorial.

Pregunta en español

Los documentos están en inglés, pero la búsqueda es por significado, así que puedes preguntar en español. Cambia QUESTIONS por:

python
QUESTIONS = [
    "Mi router Orbit X2 muestra el error E-512. ¿Qué significa? ¿Lo desconecto?",
    "¿Cuántos años de garantía tiene el router Orbit X2 Pro?",
]

Y agrega un mensaje de sistema que fije el idioma de la respuesta:

python
        r = client.chat(model="qwen2.5:7b",
                        messages=[{"role": "system", "content": "Responde siempre en español."},
                                  {"role": "user", "content": q}],
                        options=options)

La salida con rag:

text
--- con rag: Mi router Orbit X2 muestra el error E-512. ¿Qué significa? ¿Lo desconecto?
El error E-512 en tu router Orbit X2 indica que hay una "Firmware signature mismatch". Esto significa que la firma del firmware no coincide con lo esperado, lo cual puede indicar un problema de compatibilidad o corrupción del firmware.

Según el contexto proporcionado, no debes apagar el router. En lugar de eso, se recomienda lo siguiente:

1. Espera 10 minutos; a menudo, el router resolverá este problema automáticamente.
2. Si después de 10 minutos la luz roja sigue encendida, intenta presionar y mantener el botón de reinicio durante 30 segundos.

Si el problema persiste después de seguir estos pasos, podrías considerar contactar al soporte técnico de Orbit para obtener asistencia adicional.
  grounding: grounded
  source:    filesystem:orbit_support/troubleshooting-error-codes.md#0
  source:    filesystem:orbit_support/warranty.md#0

--- con rag: ¿Cuántos años de garantía tiene el router Orbit X2 Pro?
El router Orbit X2 Pro tiene una garantía de 5 años.
  grounding: grounded
  source:    filesystem:orbit_support/troubleshooting-error-codes.md#0
  source:    filesystem:orbit_support/warranty.md#0

Sin rag, el modelo también adivinó en español: dijo que el E-512 suele ser un problema de conexión a Internet o del módem y sugirió apagar y reiniciar el router y el módem. Para la garantía, dijo que los routers suelen tener de 1 a 3 años.

Dos cosas que vimos al preguntar en español:

  • Sin el mensaje de sistema, el modelo cambió de idioma a mitad de la respuesta. Empezó en español, con los datos correctos, y terminó repitiendo los pasos en chino. Con "Responde siempre en español." la respuesta salió completa en español en las dos corridas.
  • En la pregunta de la garantía, warranty.md llegó segundo. El primer fragmento fue el de códigos de error. La respuesta salió bien porque top_k: 2 pasó los dos fragmentos al modelo. Con top_k: 1, la misma pregunta en español solo recibió el fragmento de códigos de error y el modelo contestó que el contexto no decía cuántos años de garantía tiene. La pregunta en inglés, con top_k: 1, sí recibió warranty.md. El modelo de embeddings, all-minilm, se entrenó con texto en inglés. Si tus documentos están en inglés y las preguntas llegan en español, deja top_k en 2 o más.

Las fuentes vienen en los encabezados

El cuerpo de la respuesta tiene el mismo formato que Ollama. La información de la búsqueda va en los encabezados HTTP. Para la pregunta del E-512 con rag:

text
x-synapcores-grounding: grounded
x-synapcores-citations: ["filesystem:orbit_support/troubleshooting-error-codes.md#0","filesystem:orbit_support/warranty.md#0"]
x-synapcores-retrieval-duration: 1580084

x-synapcores-retrieval-duration es el tiempo que tomó la búsqueda.

Con x-synapcores-citations, tu interfaz puede poner "Fuente: troubleshooting-error-codes.md" debajo de la respuesta. El sufijo #0 es el número de fragmento dentro del archivo, empezando en 0. Estos documentos son cortos y caben en un fragmento cada uno, así que todos terminan en #0; un archivo largo da #1, #2 y así.

Ojo con grounded: significa que la respuesta se armó con fragmentos de tus documentos, no que los documentos contestaran la pregunta. Lo vas a ver en el paso 7.

Paso 6: edita un documento y mira cómo cambia la respuesta

La colección vigila la carpeta. No hay que volver a indexar ni reiniciar. Cambia la garantía del Orbit X2 Pro de 5 a 6 años:

bash
sed -i.bak 's/| Orbit X2 Pro router | 5 years |/| Orbit X2 Pro router | 6 years |/' ~/rag-ollama/docs/warranty.md

Y repite la pregunta de la garantía con rag cada pocos segundos:

text
t+  0.2s  The Orbit X2 Pro router comes with a 5-year warranty.  | grounding=grounded | first source=filesystem:orbit_support/warranty.md#0
t+  2.7s  The Orbit X2 Pro router comes with a 5-year warranty.  | grounding=grounded | first source=filesystem:orbit_support/warranty.md#0
t+  5.2s  The Orbit X2 Pro router comes with a 6-year warranty.  | grounding=grounded | first source=filesystem:orbit_support/warranty.md#0
t+  7.7s  The Orbit X2 Pro router comes with a 6-year warranty.  | grounding=grounded | first source=filesystem:orbit_support/warranty.md#0

Unos 5 segundos entre guardar el archivo y la respuesta nueva, sin reiniciar nada. Lo repetimos tres veces y el cambio llegó siempre entre los 2,6 y los 5,2 segundos. El warranty.md.bak que deja sed no se indexa, porque la colección solo toma archivos md. Al terminar, deja el archivo como estaba:

bash
mv ~/rag-ollama/docs/warranty.md.bak ~/rag-ollama/docs/warranty.md

En el rag de siempre, este paso es "acuérdate de volver a correr la ingesta".

Paso 7: rag sin tocar el código, con un alias de modelo

Si tienes varias herramientas que hablan la API de Ollama (una interfaz de chat, un script de tickets, un plugin del editor), agregar rag en cada una cansa. Puedes fijar la búsqueda en el servidor, atada a un nombre de modelo.

Agrega esto a gateway.toml y reinicia SynapCores:

toml
[ollama.aliases.orbit-support]
model = "qwen2.5:7b"
tenant_id = "<el tenant_id de la respuesta del paso 3>"

[ollama.aliases.orbit-support.grounding]
source = "filesystem:orbit_support"
mode = "inject"
top_k = 2
strict = true
  • model: el modelo que genera la respuesta.
  • source: de dónde busca. filesystem: es la colección del paso 3.
  • strict = true: si la búsqueda falla, devuelve un error en vez de dejar que el modelo conteste de memoria. Para soporte, un error es mejor que una respuesta inventada.

Vuelve a correr check.py:

text
all-minilm:latest
orbit-support
qwen2.5:7b

orbit-support aparece en la lista de modelos como uno más. Quien lo use solo cambia el nombre del modelo:

python
for q in ["What does error E-700 mean on the Orbit X2 Pro?",
          "Do you price match other retailers?"]:
    r = client.chat(model="orbit-support",
                    messages=[{"role": "user", "content": q}],
                    options={"temperature": 0, "seed": 7})
    print(f"\n--- orbit-support: {q}")
    print(r.message.content.strip())
    print("  grounding:", seen.get("x-synapcores-grounding"))
    for src in json.loads(seen.get("x-synapcores-citations") or "[]"):
        print("  source:   ", src)
text
--- orbit-support: What does error E-700 mean on the Orbit X2 Pro?
Error E-700 on the Orbit X2 Pro indicates that the mesh backhaul signal is too weak. To resolve this issue, you should move the M1 node within 10 meters of the router or use a wired backhaul to strengthen the connection.
  grounding: grounded
  source:    filesystem:orbit_support/troubleshooting-error-codes.md#0
  source:    filesystem:orbit_support/warranty.md#0

--- orbit-support: Do you price match other retailers?
The provided context does not contain information about price matching policies. Therefore, based on the given information alone, I cannot answer whether Orbit Networks matches prices from other retailers or not.
  grounding: grounded
  source:    filesystem:orbit_support/shipping.md#0
  source:    filesystem:orbit_support/returns-and-refunds.md#0

La segunda pregunta no está en ningún documento, y el modelo lo dice: The provided context does not contain information about price matching policies. Mucho mejor que inventar una política de precios.

Pero mira el encabezado: también dice grounded. La búsqueda trajo fragmentos (de envíos y devoluciones), aunque no tenían la respuesta. Para saber si tus documentos cubren la pregunta, lee la respuesta, no solo el encabezado.

Un consejo de despliegue: el alias pertenece a un tenant (tenant_id). Si pones la base de conocimiento en su propio tenant y repartes claves de solo lectura de ese tenant, cada herramienta ve esos documentos y nada más.

Bonus: el mismo rag en SQL, con EMBED()

Si tus textos ya están en tablas (tickets, notas, políticas), no necesitas una carpeta. El rag se escribe en SQL: EMBED() convierte texto en vector dentro de la base de datos, COSINE_SIMILARITY() ordena por significado y GENERATE() llama al modelo local. Todo con POST /v1/query/execute.

sql
CREATE TABLE IF NOT EXISTS recipe_rag_cited (
  chunk_id   INTEGER PRIMARY KEY,
  doc_ref    TEXT,
  content    TEXT,
  embedding  VECTOR(384)
);

INSERT INTO recipe_rag_cited (chunk_id, doc_ref, content) VALUES
 (1,'SecurityPolicy §2.1','All customer data is encrypted at rest using AES-256.'),
 (2,'SecurityPolicy §4.2','Access to production databases requires multi-factor authentication and is logged.'),
 (3,'Privacy §3.0','Personal data is retained for 24 months after account closure, then permanently deleted.'),
 (4,'SLA §1.4','The service guarantees 99.9% monthly uptime, with credits for any breach.'),
 (5,'Privacy §5.1','Customers may request a full export or deletion of their data within 30 days.'),
 (6,'SecurityPolicy §6.0','Third-party vendors are reviewed annually and must sign a data-processing agreement.');

UPDATE recipe_rag_cited SET embedding = EMBED(content);

La columna doc_ref es la cita: viaja con cada fragmento. Busca los tres más cercanos a la pregunta:

sql
SELECT chunk_id, doc_ref, content,
       COSINE_SIMILARITY(embedding, EMBED('How is customer data protected and who can access it?')) AS relevance
FROM recipe_rag_cited
ORDER BY relevance DESC
LIMIT 3;
text
chunk_id | doc_ref             | relevance
---------+---------------------+----------
       1 | SecurityPolicy §2.1 | 0.514
       2 | SecurityPolicy §4.2 | 0.401
       3 | Privacy §3.0        | 0.396

Los dos fragmentos que responden la pregunta (cifrado y acceso) salen primero. La pregunta no dice "encrypted" ni "multi-factor"; la búsqueda es por significado.

Los pasos para guardar esos tres fragmentos y pasarlos a GENERATE() con la instrucción de citar cada fuente están en la receta rag con citas (en inglés). La respuesta sale con las citas entre paréntesis. En nuestra corrida, con qwen2.5:7b:

text
Customer data is protected by being encrypted at rest using AES-256 (SecurityPolicy §2.1). Access to production databases requires multi-factor authentication and all accesses are logged (SecurityPolicy §4.2).

La diferencia con el rag de siempre es la misma: el embedding, la búsqueda y el modelo corren en la base de datos donde ya están tus filas, así que puedes agregar un WHERE normal (por cliente, por fecha, por producto) en la misma consulta que busca por significado.

Con DeepSeek: cambia el nombre del modelo

Los nombres de los modelos son los de Ollama. El modelo se descarga en el servidor, no desde el cliente: client.pull() responde 404, porque SynapCores no implementa /api/pull. En la terminal donde corre la base de datos:

bash
synapcores --config ~/.synapcores/gateway.toml pull deepseek-r1:7b

Cuando check.py muestre deepseek-r1:7b, en ask.py cambia model="qwen2.5:7b" por model="deepseek-r1:7b".

La respuesta del E-512 con rag (recortada en los pasos):

text
--- con rag: My Orbit X2 router shows error E-512. What does it mean and should I unplug it?
<think>
Okay, the user has an Orbit X2 router showing error code E-512. ... Looking at the troubleshooting section for E-512: "Firmware signature mismatch. Do NOT power off. Wait 10 minutes; the router rolls back automatically. If the light stays red, hold the reset button for 30 seconds." ...
</think>

The error code **E-512** on your Orbit X2 router indicates a firmware signature mismatch. ...

### Steps to Address the Issue:
1. **Do Not Power Off**: Unplugging the router will not resolve this issue.
2. **Wait 10 Minutes**: Allow the router to attempt to roll back to its previous state automatically.
3. **Check the Status Light**:
   - If the light remains red after waiting, hold the reset button for **30 seconds**.
...
  grounding: grounded
  source:    filesystem:orbit_support/troubleshooting-error-codes.md#0
  source:    filesystem:orbit_support/warranty.md#0

El razonamiento de deepseek-r1 llega dentro de message.content, en un bloque <think>…</think> al principio (el campo message.thinking viene vacío). Ahí se ve que citó la línea exacta del documento. Si no quieres mostrarlo, quítalo en tu app:

python
import re
answer = re.sub(r"<think>.*?</think>\s*", "", r.message.content, flags=re.S)

Solo un modelo de chat queda cargado en memoria a la vez. La primera llamada después de cambiar de modelo tarda unos segundos más mientras lo carga: en nuestra máquina, 3,4 segundos la primera llamada a qwen2.5:7b después de usar deepseek-r1:7b, y 0,1 segundos la siguiente.

Cuándo NO necesitas esto

  • Tienes un puñado de documentos cortos que no cambian. Si caben en el prompt, pégalos y listo. No hace falta rag.
  • Ya tienes un pipeline de rag que funciona y nadie edita los documentos. Si la ingesta corre una vez y ya, el ahorro es menor. Esto rinde cuando los documentos cambian seguido, cuando quieres citas sin escribir código para ellas o cuando quieres buscar en tus tablas y en tus documentos desde el mismo lugar.
  • Necesitas funciones de Ollama que no son de chat, como descargar modelos desde el cliente (/api/pull no existe en SynapCores). Revisa primero qué endpoints usa tu herramienta.

Resumen

  • En tu app: el cliente oficial de Ollama, con otro host y una clave. Para responder con tus documentos, agrega rag a options. O usa un alias y cambia solo el nombre del modelo.
  • En el servidor: registras la carpeta una vez. Si un documento cambia, se vuelve a indexar solo.
  • Cada respuesta trae sus fuentes: x-synapcores-citations y x-synapcores-grounding, listas para mostrar en tu interfaz.
  • Todo en local: el modelo corre en tu máquina y los documentos no salen de ella.

Siguiente paso

Preguntas frecuentes

¿Qué es rag con Ollama?

Rag (retrieval-augmented generation) significa recuperar fragmentos de tus documentos y dárselos al modelo antes de que responda. Con Ollama, el modelo corre en tu máquina, así que ni las preguntas ni los documentos salen de ella. Lo normal es armarlo con cuatro piezas: un cargador de documentos, un modelo de embeddings, una base de datos vectorial y el código que une todo.

¿Necesito tener Ollama instalado?

No. SynapCores implementa la API HTTP de Ollama y corre el modelo dentro del mismo proceso que la base de datos. Lo que mantienes es el cliente: la librería oficial ollama de Python funciona apuntando su host a SynapCores. Los nombres de los modelos son los mismos que en Ollama, por ejemplo qwen2.5:7b.

¿Cómo sé de qué documento salió la respuesta?

Cada respuesta con rag trae dos encabezados HTTP: x-synapcores-citations, con la lista de fragmentos que se usaron (por ejemplo filesystem:orbit_support/warranty.md#0), y x-synapcores-grounding. Tu interfaz puede mostrar las fuentes debajo de la respuesta.

Si cambio un documento, ¿tengo que volver a indexar?

No. La colección vigila la carpeta. En nuestra prueba, la respuesta cambió unos 5 segundos después de guardar el archivo editado, sin reiniciar nada.

¿Puedo hacer rag sin cambiar el código de mis herramientas?

Sí. Defines un alias de modelo en el servidor que fija la fuente de documentos. El alias aparece en la lista de modelos como uno más, y con el cliente de Ollama basta con cambiar el nombre del modelo.