JARVIS elige modelo, backend Wayland, y roadmap para la comunidad
- Seleccion de modelo: JARVIS cambia su cerebro en caliente (elegir_modelo). - Ventanas en Wayland: backend para Sway e Hyprland (ventanas-wayland.sh). - ROADMAP.md y CONTRIBUTING.md: tareas acotadas y como empezar, para animar commits (mas gestores, mas acciones, probar otros modelos, el RAG). - docs/rag.md: tabla explicita de que se publica y que no del RAG; nota de que un clon recien hecho ya tiene RAG util (man pages + glosario) sin datos privados. - README enlaza el roadmap y menciona la eleccion de modelo.
This commit is contained in:
parent
fde0fc1c72
commit
6a81cef3be
8 changed files with 375 additions and 9 deletions
87
CONTRIBUTING.md
Normal file
87
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
# Contribuir
|
||||||
|
|
||||||
|
JARVIS es software libre (GPL-3.0) y se agradecen los commits. Esta guía dice
|
||||||
|
cómo está montado, cómo probarlo y cómo añadir lo más común. Las ideas acotadas
|
||||||
|
están en [ROADMAP.md](ROADMAP.md).
|
||||||
|
|
||||||
|
## Cómo está montado
|
||||||
|
|
||||||
|
```
|
||||||
|
nucleo/jarvis.py el bucle: oír → pensar → hacer → hablar
|
||||||
|
nucleo/oido/ whisper.cpp (STT) y la captura del micrófono
|
||||||
|
nucleo/cerebro/ Ollama, tool-calling, el RAG y la elección de modelo
|
||||||
|
nucleo/boca/ Piper / voz clonada (TTS)
|
||||||
|
nucleo/manos/ las acciones, el diario, el sudo, la fonética
|
||||||
|
nucleo/cara/ el panel web (servidor WebSocket + HUD en cara/hud/)
|
||||||
|
nucleo/saber/ el RAG: indexa, busca, y enriquece/ para mejorarlo
|
||||||
|
config/ scripts: modelo, voz, whisper, ventanas
|
||||||
|
```
|
||||||
|
|
||||||
|
Nada de esto necesita compilar: es Python, bash y una página web. Se edita y se
|
||||||
|
prueba en el sitio.
|
||||||
|
|
||||||
|
## Probarlo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./install.sh # entorno, modelo de Ollama, lanzador de escritorio
|
||||||
|
./nucleo/arranca.sh # el asistente y el panel en 127.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
El panel se puede abrir **sin arrancar nada**, para trabajar el diseño:
|
||||||
|
`nucleo/cara/hud/index.html` en un navegador se llena solo con datos de ejemplo.
|
||||||
|
|
||||||
|
Hay dos suites de comprobaciones (`nucleo/pruebas/`) y un eval del RAG
|
||||||
|
(`nucleo/saber/enriquece/evalua.py`, mide `hit@k`). Si tocas el RAG, corre el
|
||||||
|
eval y pon los números en el commit.
|
||||||
|
|
||||||
|
## Añadir una acción
|
||||||
|
|
||||||
|
Una acción es una entrada en `nucleo/datos/acciones.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "brillo_sube",
|
||||||
|
"grupo": "sistema",
|
||||||
|
"frases": ["sube el brillo", "más brillo", "más luz"],
|
||||||
|
"acuse": "Voy, señor.",
|
||||||
|
"comando": "brightnessctl set +10%",
|
||||||
|
"host": true,
|
||||||
|
"respuesta": "Brillo al {campo1}, señor.",
|
||||||
|
"confirmar": false,
|
||||||
|
"captura": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `frases`: cómo se dice, con variantes. Se emparejan ANTES de llamar al modelo,
|
||||||
|
así que una acción del catálogo es instantánea.
|
||||||
|
- `comando`: shell. `{campoN}` en `respuesta` es el enésimo campo de la salida.
|
||||||
|
- `confirmar: true` para lo que borra o cambia; `captura: true` si necesita un
|
||||||
|
argumento hablado ("busca el fichero **X**").
|
||||||
|
|
||||||
|
Si el comando depende de una herramienta que puede no estar, que degrade con
|
||||||
|
gracia (mira `config/ventanas.sh`).
|
||||||
|
|
||||||
|
## Probar con otro modelo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
JARVIS_MODELO=qwen3.5:9b ./nucleo/arranca.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
O, en marcha, dile a JARVIS "usa un modelo más potente" (usa la herramienta
|
||||||
|
`elegir_modelo`). Para medir si un modelo hace bien el tool-calling, mira el
|
||||||
|
patrón del eval del RAG y adáptalo.
|
||||||
|
|
||||||
|
## Un backend de ventanas nuevo (Wayland u otro)
|
||||||
|
|
||||||
|
`config/ventanas-wayland.sh` es el ejemplo: detecta el compositor y traduce los
|
||||||
|
verbos (maximiza, izquierda, centro...) a sus órdenes. Para KDE, GNOME Wayland u
|
||||||
|
otro, se copia el patrón. Actúa sobre la ventana ACTIVA (en Wayland no se mueven
|
||||||
|
ventanas ajenas). Prueba cada orden en tu compositor antes del commit.
|
||||||
|
|
||||||
|
## Estilo
|
||||||
|
|
||||||
|
- Registro técnico y conciso, en castellano. Los comentarios explican el
|
||||||
|
**porqué**, no el qué.
|
||||||
|
- Los commits describen la decisión, no solo el cambio.
|
||||||
|
- Sin datos personales en el repo: los apuntes indexados, el diario y la voz de
|
||||||
|
referencia son del usuario (ver [PRIVACIDAD.md](PRIVACIDAD.md)).
|
||||||
12
README.md
12
README.md
|
|
@ -104,6 +104,10 @@ preguntas de pentesting, la recuperación sube de **hit@1 48 % → 82 %** y
|
||||||
**hit@5 77 % → 95 %**. El método, el pipeline y esos números están en
|
**hit@5 77 % → 95 %**. El método, el pipeline y esos números están en
|
||||||
[docs/rag.md](docs/rag.md).
|
[docs/rag.md](docs/rag.md).
|
||||||
|
|
||||||
|
Y **JARVIS elige su propio cerebro**: si una pregunta es más dura de lo normal
|
||||||
|
puede cambiar a un modelo más potente (`elegir_modelo`), y volver al rápido para
|
||||||
|
lo simple. Dile "usa un modelo más potente" o fija uno con `JARVIS_MODELO`.
|
||||||
|
|
||||||
## Órdenes con root — importante
|
## Órdenes con root — importante
|
||||||
|
|
||||||
Algunas órdenes que gestionan el sistema (instalar un paquete, reiniciar un
|
Algunas órdenes que gestionan el sistema (instalar un paquete, reiniciar un
|
||||||
|
|
@ -148,6 +152,14 @@ Proyecto en desarrollo. En el radar: acabar de generalizar los scripts de `voz/`
|
||||||
**Wayland** (hoy solo X11), y el fine-tuning de `entrena/`, pendiente de un
|
**Wayland** (hoy solo X11), y el fine-tuning de `entrena/`, pendiente de un
|
||||||
ajuste para modelos multimodales.
|
ajuste para modelos multimodales.
|
||||||
|
|
||||||
|
## Contribuir
|
||||||
|
|
||||||
|
Se agradecen los commits: es software libre y hay mucho por hacer. Las ideas
|
||||||
|
acotadas están en [ROADMAP.md](ROADMAP.md) (más gestores de ventanas, más
|
||||||
|
acciones, probar otros modelos, el RAG...) y cómo empezar en
|
||||||
|
[CONTRIBUTING.md](CONTRIBUTING.md). No hay que compilar nada: es Python, bash y
|
||||||
|
una página web.
|
||||||
|
|
||||||
## Licencia
|
## Licencia
|
||||||
|
|
||||||
[GPL-3.0](LICENSE). Software libre y copyleft: si lo modificas y lo distribuyes,
|
[GPL-3.0](LICENSE). Software libre y copyleft: si lo modificas y lo distribuyes,
|
||||||
|
|
|
||||||
80
ROADMAP.md
Normal file
80
ROADMAP.md
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
# Roadmap
|
||||||
|
|
||||||
|
Cosas por hacer, ordenadas por dónde aporta más un commit. No es una lista de
|
||||||
|
deseos: cada punto está acotado para que se pueda coger, hacer y medir. Si algo
|
||||||
|
te interesa, [CONTRIBUTING.md](CONTRIBUTING.md) dice cómo probarlo.
|
||||||
|
|
||||||
|
## Ventanas en más gestores
|
||||||
|
|
||||||
|
El motor de ventanas es X11 (`config/ventanas.sh`, con `xdotool`). En Wayland ya
|
||||||
|
hay un backend para **Sway e Hyprland** (`config/ventanas-wayland.sh`), pero está
|
||||||
|
a medias:
|
||||||
|
|
||||||
|
- **Terminar Sway/Hyprland.** `maximiza`, `restaura` y `minimiza` son fiables;
|
||||||
|
las mitades (izquierda/derecha/centro) están escritas de la documentación y
|
||||||
|
**necesitan probarse** en un equipo real —el flotante y las unidades (`ppt`,
|
||||||
|
`%`) cambian por versión—. Es un primer commit ideal si usas uno de los dos.
|
||||||
|
- **KDE Plasma (Wayland).** Vía `kdotool` o scripting de KWin. Falta el backend.
|
||||||
|
- **GNOME Wayland.** No hay API general para mover ventanas ajenas (es su modelo
|
||||||
|
de seguridad); requiere una extensión. Documentar la limitación o escribir el
|
||||||
|
puente a una extensión.
|
||||||
|
- **Mover entre pantallas y a escritorios virtuales** en Wayland: el X11 lo hace,
|
||||||
|
los backends de Wayland aún no.
|
||||||
|
|
||||||
|
## Más acciones
|
||||||
|
|
||||||
|
El catálogo son 150 órdenes (`nucleo/datos/acciones.json`). Cada una es JSON:
|
||||||
|
frases que la disparan, un comando de shell, y una plantilla de respuesta. Añadir
|
||||||
|
una es un commit pequeño y satisfactorio. Ideas que faltan:
|
||||||
|
|
||||||
|
- **Multimedia**: play/pausa, siguiente, volumen (`playerctl`, `wpctl`).
|
||||||
|
- **Brillo** de pantalla (`brightnessctl`).
|
||||||
|
- **Portapapeles**: leer/copiar (`wl-clipboard` / `xclip`).
|
||||||
|
- **Capturas** de pantalla (`grim`, `spectacle`, `gnome-screenshot`).
|
||||||
|
- **Energía**: bloquear, suspender, apagar (con confirmación).
|
||||||
|
- **Red**: a qué wifi conectar (`nmcli`).
|
||||||
|
- **Notificaciones** recientes, calendario, temporizadores.
|
||||||
|
|
||||||
|
Al añadir acciones que dependan de una herramienta, degradar con gracia si no
|
||||||
|
está (como hacen las de ventanas).
|
||||||
|
|
||||||
|
## Probar con otros modelos
|
||||||
|
|
||||||
|
JARVIS ya puede **elegir su modelo** en caliente (herramienta `elegir_modelo`, y
|
||||||
|
`JARVIS_MODELO` para fijarlo). Lo que falta es saber cuáles van bien:
|
||||||
|
|
||||||
|
- **Medir el tool-calling** de más modelos locales (llama, mistral, gemma,
|
||||||
|
phi...) con el mismo criterio del RAG (`nucleo/saber/enriquece/eval_set.jsonl`
|
||||||
|
da la idea). Cuáles emiten `tool_calls` fiables y cuáles no.
|
||||||
|
- **Auto-escalado**: que suba a un modelo potente solo cuando la pregunta lo
|
||||||
|
pida, y vuelva al rápido. Hoy lo decide el propio modelo; se puede afinar.
|
||||||
|
- **Un modelo pensado para voz**: respuestas cortas, sin divagar.
|
||||||
|
|
||||||
|
## El RAG
|
||||||
|
|
||||||
|
- **Reranker**: un cross-encoder sobre los primeros k. Medido que un embedder
|
||||||
|
transformer no compensa (ver [docs/rag.md](docs/rag.md)); un reranker es la
|
||||||
|
otra palanca sin probar.
|
||||||
|
- **Corpus de demostración público** para poder ver el RAG funcionando sin los
|
||||||
|
apuntes privados de nadie (man pages, docs de una herramienta libre).
|
||||||
|
- **Troceado que mantenga el bloque de comandos con su encabezado** (hoy separa
|
||||||
|
algún comando de su contexto).
|
||||||
|
|
||||||
|
## Voz
|
||||||
|
|
||||||
|
- **Más idiomas.** Hoy es castellano de punta a punta (STT, prompt, TTS). Piper
|
||||||
|
y whisper tienen modelos de otros idiomas; falta parametrizarlo.
|
||||||
|
- **Fine-tuning de la voz** (Piper/VITS) para clonar mejor con poco audio.
|
||||||
|
|
||||||
|
## Fine-tuning del modelo
|
||||||
|
|
||||||
|
`entrena/` tiene el pipeline QLoRA, bloqueado porque `qwen3.5` es multimodal y
|
||||||
|
`FastLanguageModel` lo mete por el camino de solo texto. El arreglo apunta a
|
||||||
|
`FastVisionModel` con `finetune_vision_layers=False`. Sin probar.
|
||||||
|
|
||||||
|
## Empaquetado
|
||||||
|
|
||||||
|
- **Perfiles de hardware** en `install.sh` (4 GB / 12 GB / solo-CPU) que ajusten
|
||||||
|
el modelo y el contexto solos.
|
||||||
|
- **Detección de rutas** más fina en los scripts de `config/` y `voz/`, que aún
|
||||||
|
asumen parte de la disposición del autor.
|
||||||
57
config/ventanas-wayland.sh
Executable file
57
config/ventanas-wayland.sh
Executable file
|
|
@ -0,0 +1,57 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Backend de ventanas para Wayland (Sway e Hyprland). ventanas.sh delega aqui
|
||||||
|
# cuando detecta uno de esos compositores.
|
||||||
|
#
|
||||||
|
# Por que un fichero aparte y no dentro de ventanas.sh: aquel es X11 puro
|
||||||
|
# (xdotool, xrandr) y esta probado; mezclar aqui lo romperia. Cada backend en su
|
||||||
|
# sitio, y este se puede iterar sin tocar el que ya funciona.
|
||||||
|
#
|
||||||
|
# En Wayland NO se pueden mover ventanas AJENAS sin permisos del compositor, asi
|
||||||
|
# que esto actua sobre la ventana ACTIVA (la que tiene el foco), que es lo que
|
||||||
|
# el compositor si deja hacer. Por eso ignora el <ventana> que si usa el X11.
|
||||||
|
#
|
||||||
|
# ── ESTADO ──────────────────────────────────────────────────────────────────
|
||||||
|
#
|
||||||
|
# maximiza / restaura / minimiza: confiables (pantalla completa y scratchpad son
|
||||||
|
# ordenes de una linea, bien documentadas en los dos compositores).
|
||||||
|
# izquierda / derecha / centro: escritas de la documentacion, A PROBAR en un
|
||||||
|
# equipo real —el flotante y las unidades (ppt, %) varian por version—. Es un
|
||||||
|
# primer commit ideal para quien use Sway o Hyprland. Ver ROADMAP.md.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
verbo=${1:-}
|
||||||
|
|
||||||
|
if command -v swaymsg >/dev/null 2>&1 && swaymsg -t get_version >/dev/null 2>&1; then
|
||||||
|
COMP=sway
|
||||||
|
elif command -v hyprctl >/dev/null 2>&1 && hyprctl version >/dev/null 2>&1; then
|
||||||
|
COMP=hypr
|
||||||
|
else
|
||||||
|
echo "no encuentro Sway ni Hyprland en marcha"; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sway() { swaymsg "$@" >/dev/null 2>&1; }
|
||||||
|
hypr() { hyprctl dispatch "$@" >/dev/null 2>&1; }
|
||||||
|
|
||||||
|
case "$verbo:$COMP" in
|
||||||
|
# --- pantalla completa: confiable ---
|
||||||
|
maximiza:sway) sway fullscreen enable; echo "Maximizada, señor" ;;
|
||||||
|
maximiza:hypr) hypr fullscreen 1; echo "Maximizada, señor" ;;
|
||||||
|
restaura:sway) sway fullscreen disable; echo "Restaurada, señor" ;;
|
||||||
|
restaura:hypr) hypr fullscreen 0; echo "Restaurada, señor" ;;
|
||||||
|
minimiza:sway) sway move scratchpad; echo "Minimizada, señor" ;;
|
||||||
|
minimiza:hypr) hypr movetoworkspacesilent special; echo "Minimizada, señor" ;;
|
||||||
|
|
||||||
|
# --- mitades y centro: A PROBAR (unidades/flotante dependen de la version) ---
|
||||||
|
izquierda:sway) sway floating enable; sway resize set width 50 ppt height 100 ppt; sway move absolute position 0 0; echo "A la izquierda, señor" ;;
|
||||||
|
derecha:sway) sway floating enable; sway resize set width 50 ppt height 100 ppt; sway move absolute position 50 ppt 0; echo "A la derecha, señor" ;;
|
||||||
|
centro:sway) sway floating enable; sway resize set width 70 ppt height 80 ppt; sway move absolute position center; echo "Al centro, señor" ;;
|
||||||
|
izquierda:hypr) hypr setfloating; hypr resizeactive exact 50% 100%; hypr moveactive exact 0 0; echo "A la izquierda, señor" ;;
|
||||||
|
derecha:hypr) hypr setfloating; hypr resizeactive exact 50% 100%; hypr moveactive exact 50% 0; echo "A la derecha, señor" ;;
|
||||||
|
centro:hypr) hypr setfloating; hypr resizeactive exact 70% 80%; hypr centerwindow; echo "Al centro, señor" ;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "en $COMP todavia no esta esa orden de ventanas. Implementadas:" \
|
||||||
|
"maximiza, restaura, minimiza, izquierda, derecha, centro." \
|
||||||
|
"Las demas (mover entre pantallas, escritorios) estan en el roadmap."
|
||||||
|
exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
@ -33,12 +33,24 @@ ALIAS_FICHERO=${JARVIS_ALIAS_PANTALLAS:-$HOME/.config/jarvis-pantallas.json}
|
||||||
|
|
||||||
hay_x11() { [ -n "${DISPLAY:-}" ] && command -v xdotool >/dev/null 2>&1; }
|
hay_x11() { [ -n "${DISPLAY:-}" ] && command -v xdotool >/dev/null 2>&1; }
|
||||||
if ! hay_x11; then
|
if ! hay_x11; then
|
||||||
# Diagnostico segun el entorno, no un "no X11" seco.
|
en_wayland() { [ "${XDG_SESSION_TYPE:-}" = "wayland" ] || [ -n "${WAYLAND_DISPLAY:-}" ]; }
|
||||||
if [ "${XDG_SESSION_TYPE:-}" = "wayland" ] || [ -n "${WAYLAND_DISPLAY:-}" ]; then
|
# En Wayland con Sway o Hyprland, delega en el backend propio (actua sobre la
|
||||||
echo "las acciones de ventanas usan xdotool (X11) y estas en Wayland" \
|
# ventana activa). El verbo va en $1 y para las ordenes de ventana el destino
|
||||||
"(${XDG_CURRENT_DESKTOP:-tu compositor}). En Wayland mover ventanas" \
|
# (izquierda/derecha/centro/maximiza...) suele venir en $3; se le pasa el que
|
||||||
"ajenas depende del compositor; por ahora estas ordenes quedan fuera." \
|
# tenga sentido.
|
||||||
"El resto de JARVIS funciona igual."
|
if en_wayland && (command -v swaymsg >/dev/null 2>&1 || command -v hyprctl >/dev/null 2>&1); then
|
||||||
|
BACKEND="$(dirname "${BASH_SOURCE[0]:-$0}")/ventanas-wayland.sh"
|
||||||
|
case "${1:-}" in
|
||||||
|
zona) exec bash "$BACKEND" "${3:-centro}" ;; # zona <ventana> <izquierda|derecha|centro>
|
||||||
|
maximiza|restaura|minimiza) exec bash "$BACKEND" "$1" ;;
|
||||||
|
*) echo "en Wayland de momento van: maximiza, restaura, minimiza," \
|
||||||
|
"y mitades (izquierda/derecha/centro). El resto, en el roadmap."; exit 1 ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
if en_wayland; then
|
||||||
|
echo "estas en Wayland (${XDG_CURRENT_DESKTOP:-tu compositor}) sin Sway ni" \
|
||||||
|
"Hyprland: mover ventanas ajenas no tiene via universal ahi. El resto" \
|
||||||
|
"de JARVIS funciona igual. Soporte por compositor: ver ROADMAP.md."
|
||||||
elif [ -n "${DISPLAY:-}" ]; then
|
elif [ -n "${DISPLAY:-}" ]; then
|
||||||
echo "estas en X11 pero falta xdotool. Instalalo con tu gestor de paquetes."
|
echo "estas en X11 pero falta xdotool. Instalalo con tu gestor de paquetes."
|
||||||
else
|
else
|
||||||
|
|
|
||||||
16
docs/rag.md
16
docs/rag.md
|
|
@ -80,3 +80,19 @@ reproduce con `enriquece/experimento_embedder.py`.
|
||||||
conocimiento personal; `diario.jsonl` es lo que se le dice al asistente. Son del
|
conocimiento personal; `diario.jsonl` es lo que se le dice al asistente. Son del
|
||||||
usuario. El `.gitignore` los bloquea. Lo reproducible —el método, el código y la
|
usuario. El `.gitignore` los bloquea. Lo reproducible —el método, el código y la
|
||||||
forma de medirlo— sí está; el corpus lo pone cada quien con sus notas.
|
forma de medirlo— sí está; el corpus lo pone cada quien con sus notas.
|
||||||
|
|
||||||
|
## Qué se publica y qué no, en concreto
|
||||||
|
|
||||||
|
| | En el repo |
|
||||||
|
|---|---|
|
||||||
|
| El código: `indexa`, `busca`, `glosario`, `sistema`, `enriquece/` | **sí** |
|
||||||
|
| El eval (`enriquece/eval_set.jsonl`) | **sí** |
|
||||||
|
| El glosario de tareas de Linux en castellano (`glosario.py`) | **sí** (es conocimiento genérico, no personal) |
|
||||||
|
| El índice (`saber.jsonl`) y sus copias | **no** — los apuntes |
|
||||||
|
| El diario (`diario.jsonl`) | **no** — las conversaciones |
|
||||||
|
| Lo derivado (`cosecha.jsonl`, `sintesis.jsonl`) | **no** — salen de los apuntes |
|
||||||
|
|
||||||
|
Un detalle útil: `indexa.py` en un equipo recién clonado ya construye un índice
|
||||||
|
**útil sin ningún dato privado** —de las *man pages* del sistema y del glosario—,
|
||||||
|
así que se puede ver el RAG funcionando de inmediato; los apuntes de COFRE solo
|
||||||
|
lo enriquecen si los tienes.
|
||||||
|
|
|
||||||
|
|
@ -262,9 +262,16 @@ class Panel:
|
||||||
self.manda_controles()
|
self.manda_controles()
|
||||||
self.cara.manda("voz", {"nombre": self.boca.voz, "id": self.boca.voz})
|
self.cara.manda("voz", {"nombre": self.boca.voz, "id": self.boca.voz})
|
||||||
if self.cerebro:
|
if self.cerebro:
|
||||||
self.cara.manda("cerebro", {"nombre": "local", "id": "local",
|
self._manda_cerebro(self.cerebro.modelo)
|
||||||
"detalle": self.cerebro.modelo,
|
# Cuando JARVIS elige otro modelo, el rotulo del boton "cerebro" se
|
||||||
"fuera": False})
|
# actualiza solo: si no, dice uno y piensa con otro.
|
||||||
|
self.cerebro.al_cambiar_modelo = self._manda_cerebro
|
||||||
|
|
||||||
|
def _manda_cerebro(self, modelo):
|
||||||
|
corto = modelo.partition(":")
|
||||||
|
corto = corto[0] if corto[2] in ("", "latest") else corto[2]
|
||||||
|
self.cara.manda("cerebro", {"nombre": corto, "id": modelo,
|
||||||
|
"detalle": modelo, "fuera": False})
|
||||||
|
|
||||||
def _bucle_telemetria(self):
|
def _bucle_telemetria(self):
|
||||||
tic = 0
|
tic = 0
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,16 @@ import urllib.request
|
||||||
ENDPOINT = os.environ.get("JARVIS_OLLAMA", "http://127.0.0.1:11434")
|
ENDPOINT = os.environ.get("JARVIS_OLLAMA", "http://127.0.0.1:11434")
|
||||||
MODELO = os.environ.get("JARVIS_MODELO", "qwen3.5:4b-jarvis")
|
MODELO = os.environ.get("JARVIS_MODELO", "qwen3.5:4b-jarvis")
|
||||||
|
|
||||||
|
|
||||||
|
def modelos_disponibles(endpoint=ENDPOINT) -> list:
|
||||||
|
"""Los modelos que tiene Ollama en disco, para poder elegir entre ellos."""
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(endpoint + "/api/tags", timeout=5) as r:
|
||||||
|
datos = json.load(r)
|
||||||
|
return sorted(m["name"] for m in datos.get("models", []))
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
# Cuantas vueltas de "llama a una herramienta -> toma el resultado" se permiten
|
# Cuantas vueltas de "llama a una herramienta -> toma el resultado" se permiten
|
||||||
# antes de cortar. Sin tope, un modelo que se atasca pidiendo lo mismo deja al
|
# antes de cortar. Sin tope, un modelo que se atasca pidiendo lo mismo deja al
|
||||||
# usuario esperando para siempre.
|
# usuario esperando para siempre.
|
||||||
|
|
@ -75,6 +85,12 @@ usa buscar_en_apuntes con palabras clave ANTES de contestar. Ahi estan los
|
||||||
comandos que el usuario ya ha probado; es mejor buscarlos que inventarlos. Si la
|
comandos que el usuario ya ha probado; es mejor buscarlos que inventarlos. Si la
|
||||||
primera busqueda no da, reformula y prueba otra vez.
|
primera busqueda no da, reformula y prueba otra vez.
|
||||||
|
|
||||||
|
Eliges tu propio cerebro. Si una pregunta es mas dificil de lo normal —razonar,
|
||||||
|
codigo, varios pasos— cambia a un modelo mas potente con elegir_modelo("potente")
|
||||||
|
antes de contestarla, y para lo simple vuelve a elegir_modelo("rapido"). Si el
|
||||||
|
usuario pide un modelo concreto, cambialo. Avisa en una frase de que has
|
||||||
|
cambiado.
|
||||||
|
|
||||||
Tu respuesta se convierte en AUDIO y se escucha en voz alta:
|
Tu respuesta se convierte en AUDIO y se escucha en voz alta:
|
||||||
- Castellano siempre. Trata al usuario de usted y llamale "señor" en la primera
|
- Castellano siempre. Trata al usuario de usted y llamale "señor" en la primera
|
||||||
frase.
|
frase.
|
||||||
|
|
@ -169,6 +185,32 @@ def herramientas(catalogo) -> list:
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "elegir_modelo",
|
||||||
|
"description": (
|
||||||
|
"Cambia el modelo con el que piensas. Usalo cuando una "
|
||||||
|
"pregunta sea mas dificil de lo normal (razonar, codigo, "
|
||||||
|
"varios pasos) y quieras uno mas potente, o cuando el usuario "
|
||||||
|
"pida expresamente cambiar de modelo. Valores: 'rapido' "
|
||||||
|
"(ligero, para lo simple), 'potente' (mas grande, mas lento), "
|
||||||
|
"'razonador' (piensa en voz alta antes de responder), o el "
|
||||||
|
"nombre exacto de un modelo de Ollama. El cambio dura hasta "
|
||||||
|
"que lo vuelvas a cambiar. Contesta breve tras cambiar."
|
||||||
|
),
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"cual": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "rapido | potente | razonador | nombre exacto",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["cual"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
|
|
@ -203,6 +245,57 @@ class Cerebro:
|
||||||
self.capas = 0 if en_ram else None
|
self.capas = 0 if en_ram else None
|
||||||
self.manos = manos # a quien pedirle que ejecute
|
self.manos = manos # a quien pedirle que ejecute
|
||||||
self.historia = []
|
self.historia = []
|
||||||
|
# A quien avisar cuando JARVIS cambia de modelo, para que el panel
|
||||||
|
# actualice el rotulo del boton "cerebro". Lo conecta el panel.
|
||||||
|
self.al_cambiar_modelo = None
|
||||||
|
|
||||||
|
# Atajos hablados -> familia de modelo. El modelo elige por INTENCION
|
||||||
|
# ("potente") y aqui se traduce a lo que hay instalado, para no obligarle a
|
||||||
|
# saberse los nombres exactos. Se resuelve contra lo que de verdad hay en
|
||||||
|
# Ollama, cogiendo el mayor/menor de la familia qwen que exista.
|
||||||
|
def _resuelve_modelo(self, cual: str) -> str | None:
|
||||||
|
cual = (cual or "").strip().lower()
|
||||||
|
hay = modelos_disponibles(self.endpoint)
|
||||||
|
if not hay:
|
||||||
|
return None
|
||||||
|
# nombre exacto o casi (permite "9b" -> "qwen3.5:9b")
|
||||||
|
for m in hay:
|
||||||
|
if cual == m.lower() or cual == m.lower().split(":")[-1]:
|
||||||
|
return m
|
||||||
|
qwen = sorted(m for m in hay if "qwen" in m.lower())
|
||||||
|
def por_tam(sufijo):
|
||||||
|
return next((m for m in qwen if m.lower().endswith(sufijo)), None)
|
||||||
|
if cual in ("rapido", "ligero", "pequeno", "pequeño"):
|
||||||
|
return por_tam(":2b") or por_tam(":4b-jarvis") or (qwen[0] if qwen else None)
|
||||||
|
if cual in ("potente", "grande", "mejor", "fuerte"):
|
||||||
|
return por_tam(":9b") or (qwen[-1] if qwen else None)
|
||||||
|
if cual in ("razonador", "razona", "piensa", "deepseek"):
|
||||||
|
return next((m for m in hay if "deepseek" in m.lower() or "-r1" in m.lower()), None)
|
||||||
|
# por defecto, el de trabajo del naranja si existe
|
||||||
|
return por_tam(":4b-jarvis") or (qwen[0] if qwen else None)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _nombre_corto(m: str) -> str:
|
||||||
|
# "qwen3.5:9b" -> "9b"; "deepseek-r1:latest" -> "deepseek-r1"
|
||||||
|
base, _, tag = m.partition(":")
|
||||||
|
return base if tag in ("", "latest") else tag
|
||||||
|
|
||||||
|
def cambia_modelo(self, cual: str) -> str:
|
||||||
|
"""Cambia el cerebro en caliente. Devuelve lo que decir tras cambiar."""
|
||||||
|
nuevo = self._resuelve_modelo(cual)
|
||||||
|
if not nuevo:
|
||||||
|
return f"no tengo ningun modelo que encaje con «{cual}», señor."
|
||||||
|
if nuevo == self.modelo:
|
||||||
|
return f"ya estoy pensando con {self._nombre_corto(nuevo)}, señor."
|
||||||
|
self.modelo = nuevo
|
||||||
|
if self.al_cambiar_modelo:
|
||||||
|
try:
|
||||||
|
self.al_cambiar_modelo(nuevo)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# Ojo: un modelo mas grande no cabe en la grafica pequeña y tirara de CPU
|
||||||
|
# —mas lento pero mas capaz—; eso es cosa de Ollama, no de aqui.
|
||||||
|
return f"Cambiado a {self._nombre_corto(nuevo)}, señor."
|
||||||
|
|
||||||
def _saber(self, texto: str) -> str:
|
def _saber(self, texto: str) -> str:
|
||||||
"""Los apuntes de COFRE que vengan a cuento, para meterlos al prompt.
|
"""Los apuntes de COFRE que vengan a cuento, para meterlos al prompt.
|
||||||
|
|
@ -283,6 +376,8 @@ class Cerebro:
|
||||||
# toca la maquina, asi que funciona aunque el candado este echado.
|
# toca la maquina, asi que funciona aunque el candado este echado.
|
||||||
if nombre == "buscar_en_apuntes":
|
if nombre == "buscar_en_apuntes":
|
||||||
return self._busca_apuntes(args.get("consulta", ""))
|
return self._busca_apuntes(args.get("consulta", ""))
|
||||||
|
if nombre == "elegir_modelo":
|
||||||
|
return self.cambia_modelo(args.get("cual", ""))
|
||||||
if self.manos is None:
|
if self.manos is None:
|
||||||
return "no hay manos conectadas"
|
return "no hay manos conectadas"
|
||||||
if nombre == "orden_del_catalogo":
|
if nombre == "orden_del_catalogo":
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue