docs(i18n): translate all docs to English (with .es.md as Spanish alternative)

- README.md: full English translation, .es.md preserved
- docs/architecture.md: full translation (1039 lines)
- configs/portfolio-bot.yaml: full English translation, .es.yaml preserved
- data/projects/README.md + example-project.md: translated with banners

Default language is now English (standard for OSS). Spanish remains
available via .es.* suffix files.
This commit is contained in:
Victor Hugo Vargas 2026-06-30 13:27:00 -07:00
parent fcf0e6251a
commit b9769fac41
10 changed files with 1658 additions and 316 deletions

109
README.es.md Normal file
View file

@ -0,0 +1,109 @@
# Rony Chat Bot — Portfolio Bot HTTP
> 🌐 **Idioma:** [English](README.md) | [Español](README.es.md)
> 🤖 **Chatbot HTTP que presenta tu portfolio y responde preguntas sobre tus proyectos.**
**Rony Chat Bot** es un chatbot basado en [`rony-llm-agent`](https://github.com/VictorVargas/rony-llm-agent) que se integra con un sitio Astro/React para responder preguntas sobre Victor Hugo Vargas y sus proyectos, usando **RAG sobre archivos markdown**.
## ✨ Features
- 🌐 **HTTP server** con streaming SSE (Server-Sent Events)
- 🧠 **RAG sobre markdown** — indexa automáticamente los `.md` en `data/projects/`
- 🎭 **Persona customizable** — responde como "asistente de Victor"
- ⚡ **Self-hosted** con Ollama o llama.cpp (no requiere API key de cloud)
- 🔌 **Integrable** con Astro/React via proxy HTTP
- 🛡️ **Rate limiting** y logging estructurado
- 📦 **Portable** — se puede adaptar a otros contextos (clientes, productos, etc.)
## 🚀 Quick start
```bash
# 1. Instalar
git clone https://github.com/VictorVargas/rony-chat-bot.git
cd chat-bot
# 2. Resolver dependencias (crea go.sum con hashes)
go mod tidy
# 3. Configurar provider (ejemplo: Ollama)
# Asegúrate de tener Ollama corriendo: ollama serve
# Modelo descargado: ollama pull qwen2.5:1.5b
# 4. Cargar tus proyectos en data/projects/
echo "# Mi Proyecto Cool\nDescripción..." > data/projects/mi-proyecto.md
# 5. Build
go build -o bin/chat-bot ./cmd/chat-bot
# 6. Run
./bin/chat-bot serve
# → Sirve en http://localhost:7331
```
## 📁 Estructura
```
chat-bot/
├── cm./rony-chat-bot/ # Entry point (CLI)
├── internal/
│ ├── server/ # HTTP handlers + SSE
│ ├── portfolio/ # Data loader (markdown → RAG)
│ ├── persona/ # Persona override
│ └── streaming/ # SSE helpers
├── data/projects/ # ← TUS PROYECTOS EN MARKDOWN
│ ├── rony-tui.md
│ ├── rony-llm-agent.md
│ └── ...
├── configs/
│ └── portfolio-bot.yaml # Provider config
├── docs/
│ └── architecture.md # ← Especificación técnica completa
└── go.mod # require rony-llm-agent
```
## 🎯 Uso desde Astro
Ver [`docs/architecture.md`](./docs/architecture.md) §5 — patrón recomendado de proxy.
```typescript
// portfolio/src/pages/api/chat.ts
export const POST: APIRoute = async ({ request }) => {
const body = await request.json();
const resp = await fetch('http://localhost:7331/api/chat', {
method: 'POST',
body: JSON.stringify(body),
});
return new Response(resp.body, {
headers: { 'Content-Type': 'text/event-stream' },
});
};
```
## 🔄 Adaptar a otro cliente
Este bot está diseñado para ser **atómico** y reusable. Para adaptarlo (ej. chatbot para un concesionario):
1. Fork/clone este repo
2. Reemplaza `data/projects/` con `data/inventory/` (u otro dominio)
3. Actualiza `configs/portfolio-bot.yaml` con la nueva persona
4. Deploy
La librería `rony-llm-agent` no cambia.
## 📚 Documentación
- [**Architecture doc**](./docs/architecture.md) — Especificación técnica completa
- [Library: `rony-llm-agent`](https://github.com/VictorVargas/rony-llm-agent) — Core reutilizable
- [Harness](https://github.com/VictorVargas/rony-harness) — El otro proyecto que usa la misma librería
## 📄 Licencia
MIT — ver [`LICENSE`](./LICENSE).
## 🔗 Proyectos del workspace
- [`rony-llm-agent`](https://github.com/VictorVargas/rony-llm-agent) — Librería core
- [`harness`](https://github.com/VictorVargas/rony-harness) — AI agent harness (TUI)
- [`portfolio`](https://github.com/VictorVargas/portfolio) — Astro + React site (integra este bot)

View file

@ -1,68 +1,70 @@
# Rony Chat Bot — Portfolio Bot HTTP # Rony Chat Bot — HTTP Portfolio Bot
> 🤖 **Chatbot HTTP que presenta tu portfolio y responde preguntas sobre tus proyectos.** > 🌐 **Language:** [English](./README.md) | [Español](./README.es.md)
>
> 🤖 **HTTP chatbot that presents your portfolio and answers questions about your projects.**
**Rony Chat Bot** es un chatbot basado en [`rony-llm-agent`](https://github.com/VictorVargas/rony-llm-agent) que se integra con un sitio Astro/React para responder preguntas sobre Victor Hugo Vargas y sus proyectos, usando **RAG sobre archivos markdown**. **Rony Chat Bot** is a chatbot based on [`rony-llm-agent`](https://github.com/VictorVargas/rony-llm-agent) that integrates with an Astro/React site to answer questions about Victor Hugo Vargas and his projects, using **RAG over markdown files**.
## ✨ Features ## ✨ Features
- 🌐 **HTTP server** con streaming SSE (Server-Sent Events) - 🌐 **HTTP server** with SSE (Server-Sent Events) streaming
- 🧠 **RAG sobre markdown** — indexa automáticamente los `.md` en `data/projects/` - 🧠 **RAG over markdown** — automatically indexes `.md` in `data/projects/`
- 🎭 **Persona customizable** — responde como "asistente de Victor" - 🎭 **Customizable persona** — responds as "Victor's assistant"
- ⚡ **Self-hosted** con Ollama o llama.cpp (no requiere API key de cloud) - ⚡ **Self-hosted** with Ollama or llama.cpp (no cloud API key required)
- 🔌 **Integrable** con Astro/React via proxy HTTP - 🔌 **Integrable** with Astro/React via HTTP proxy
- 🛡️ **Rate limiting** y logging estructurado - 🛡️ **Rate limiting** and structured logging
- 📦 **Portable**se puede adaptar a otros contextos (clientes, productos, etc.) - 📦 **Portable**adaptable to other contexts (clients, products, etc.)
## 🚀 Quick start ## 🚀 Quick start
```bash ```bash
# 1. Instalar # 1. Install
git clone https://github.com/VictorVargas/rony-chat-bot.git git clone https://github.com/VictorVargas/rony-chat-bot.git
cd chat-bot cd rony-chat-bot
# 2. Resolver dependencias (crea go.sum con hashes) # 2. Resolve dependencies (creates go.sum with hashes)
go mod tidy go mod tidy
# 3. Configurar provider (ejemplo: Ollama) # 3. Configure provider (e.g., Ollama)
# Asegúrate de tener Ollama corriendo: ollama serve # Make sure Ollama is running: ollama serve
# Modelo descargado: ollama pull qwen2.5:1.5b # Downloaded model: ollama pull qwen2.5:1.5b
# 4. Cargar tus proyectos en data/projects/ # 4. Load your projects in data/projects/
echo "# Mi Proyecto Cool\nDescripción..." > data/projects/mi-proyecto.md echo "# My Cool Project\nDescription..." > data/projects/my-project.md
# 5. Build # 5. Build
go build -o bin/chat-bot ./cmd/chat-bot go build -o bin/chat-bot ./cmd/chat-bot
# 6. Run # 6. Run
./bin/chat-bot serve ./bin/chat-bot serve
# → Sirve en http://localhost:7331 # → Serves on http://localhost:7331
``` ```
## 📁 Estructura ## 📁 Structure
``` ```
chat-bot/ chat-bot/
├── cm./rony-chat-bot/ # Entry point (CLI) ├── cmd/chat-bot/ # Entry point (CLI)
├── internal/ ├── internal/
│ ├── server/ # HTTP handlers + SSE │ ├── server/ # HTTP handlers + SSE
│ ├── portfolio/ # Data loader (markdown → RAG) │ ├── portfolio/ # Data loader (markdown → RAG)
│ ├── persona/ # Persona override │ ├── persona/ # Persona override
│ └── streaming/ # SSE helpers │ └── streaming/ # SSE helpers
├── data/projects/ # ← TUS PROYECTOS EN MARKDOWN ├── data/projects/ # ← YOUR PROJECTS IN MARKDOWN
│ ├── rony-tui.md │ ├── rony-harness.md
│ ├── rony-llm-agent.md │ ├── rony-llm-agent.md
│ └── ... │ └── ...
├── configs/ ├── configs/
│ └── portfolio-bot.yaml # Provider config │ └── portfolio-bot.yaml # Provider config
├── docs/ ├── docs/
│ └── architecture.md # ← Especificación técnica completa │ └── architecture.md # ← Complete technical specification
└── go.mod # require rony-llm-agent └── go.mod # require rony-llm-agent
``` ```
## 🎯 Uso desde Astro ## 🎯 Use from Astro
Ver [`docs/architecture.md`](./docs/architecture.md) §5 — patrón recomendado de proxy. See [`docs/architecture.md`](./docs/architecture.md) §5 — recommended proxy pattern.
```typescript ```typescript
// portfolio/src/pages/api/chat.ts // portfolio/src/pages/api/chat.ts
@ -70,37 +72,43 @@ export const POST: APIRoute = async ({ request }) => {
const body = await request.json(); const body = await request.json();
const resp = await fetch('http://localhost:7331/api/chat', { const resp = await fetch('http://localhost:7331/api/chat', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body), body: JSON.stringify(body),
}); });
return new Response(resp.body, { return new Response(resp.body, {
headers: { 'Content-Type': 'text/event-stream' }, status: 200,
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
}); });
}; };
``` ```
## 🔄 Adaptar a otro cliente ## 🔄 Adapt to another client
Este bot está diseñado para ser **atómico** y reusable. Para adaptarlo (ej. chatbot para un concesionario): This bot is designed to be **atomic** and reusable. To adapt it (e.g., chatbot for a car dealership):
1. Fork/clone este repo 1. Fork/clone this repo
2. Reemplaza `data/projects/` con `data/inventory/` (u otro dominio) 2. Replace `data/projects/` with `data/inventory/` (or another domain)
3. Actualiza `configs/portfolio-bot.yaml` con la nueva persona 3. Update `configs/portfolio-bot.yaml` with the new persona
4. Deploy 4. Deploy
La librería `rony-llm-agent` no cambia. The `rony-llm-agent` library doesn't change.
## 📚 Documentación ## 📚 Documentation
- [**Architecture doc**](./docs/architecture.md) — Especificación técnica completa - [**Architecture doc**](./docs/architecture.md) — Complete technical specification
- [Library: `rony-llm-agent`](https://github.com/VictorVargas/rony-llm-agent) — Core reutilizable - [Library: `rony-llm-agent`](https://github.com/VictorVargas/rony-llm-agent) — Reusable core
- [Harness](https://github.com/VictorVargas/rony-harness) — El otro proyecto que usa la misma librería - [Rony Harness](https://github.com/VictorVargas/rony-harness) — The other project using the same library
## 📄 Licencia ## 📄 License
MIT — ver [`LICENSE`](./LICENSE). MIT — see [`LICENSE`](./LICENSE).
## 🔗 Proyectos del workspace ## 🔗 Workspace projects
- [`rony-llm-agent`](https://github.com/VictorVargas/rony-llm-agent) — Librería core - [`rony-llm-agent`](https://github.com/VictorVargas/rony-llm-agent) — Core library
- [`harness`](https://github.com/VictorVargas/rony-harness) — AI agent harness (TUI) - [`rony-harness`](https://github.com/VictorVargas/rony-harness) — AI agent harness (TUI)
- [`portfolio`](https://github.com/VictorVargas/portfolio) — Astro + React site (integra este bot) - [`portfolio`](https://github.com/VictorVargas/portfolio) — Astro + React site (integrates this bot)

View file

@ -0,0 +1,82 @@
# Configuración del Portfolio Bot
# Documentación: https://github.com/VictorVargas/rony-llm-agent/pkg/llm
server:
host: "0.0.0.0"
port: 7331
read_timeout_ms: 30000
cors_origins:
- "http://localhost:4321" # Astro dev server
- "https://victorvargas.dev" # Producción (cuando exista)
rate_limit:
requests_per_minute: 30 # Por IP
burst: 5
# Providers LLM (al menos uno configurado)
providers:
# === Ollama (recomendado para desarrollo) ===
- name: ollama-local
type: ollama
model: qwen2.5:1.5b # Modelo pequeño para Q&A
endpoint: http://localhost:11434
default: true
# === llama.cpp directo (GGUF) ===
- name: llamacpp-local
type: llamacpp
model_path: ${RONY_MODELS_PATH}/qwen2.5-1.5b-instruct-q5_k_m.gguf
context_size: 4096
n_gpu_layers: 999
# === Anthropic (si quieres calidad > privacidad) ===
- name: anthropic-api
type: anthropic
model: claude-haiku-4 # Modelo barato
api_key_env: ANTHROPIC_API_KEY
# RAG: cómo se indexan los proyectos
rag:
enabled: true
data_path: ./data/projects # Directorio con .md
chunk_size: 500 # caracteres por chunk
chunk_overlap: 50
embedding_provider: ollama # o llamacpp
embedding_model: nomic-embed-text
vector_db_path: ./chroma # Persistencia local
top_k: 5 # Documentos a recuperar por query
rerank: false # Phase 2
# Persona: quién es el bot
persona:
name: "Rony Chat Bot"
tone: "Profesional, conocedor, amable"
language: "Español"
constraints:
- "Solo responder sobre Victor y sus proyectos"
- "Si no sabes, decir 'No tengo esa información'"
- "Ser conciso pero informativo"
- "Usar formato markdown para listas y código"
intro: "¡Hola! Soy Rony, el asistente virtual de Victor Hugo Vargas. Pregúntame sobre sus proyectos, skills o experiencia."
# System prompt base (concatenado con el contenido RAG)
system_prompt: |
Eres Rony Chat Bot, el asistente virtual de Victor Hugo Vargas, un ingeniero de software mexicano.
Tu trabajo es responder preguntas sobre:
- Los proyectos de Victor (ver archivos en data/projects/)
- Su experiencia y skills técnicas
- Su enfoque de trabajo
Responde en español, con tono profesional pero accesible.
Si te preguntan algo que no está en tu contexto, dilo honestamente.
Formato recomendado:
- Usa markdown para listas, código, y énfasis
- Sé conciso (máximo 2-3 párrafos por respuesta)
- Incluye links a repos cuando sea relevante
# Logging
logging:
level: info # debug | info | warn | error
format: json # json | text
output: stderr

View file

@ -1,5 +1,5 @@
# Configuración del Portfolio Bot # Portfolio Bot Configuration
# Documentación: https://github.com/VictorVargas/rony-llm-agent/pkg/llm # Documentation: https://github.com/VictorVargas/rony-llm-agent/pkg/llm
server: server:
host: "0.0.0.0" host: "0.0.0.0"
@ -7,73 +7,73 @@ server:
read_timeout_ms: 30000 read_timeout_ms: 30000
cors_origins: cors_origins:
- "http://localhost:4321" # Astro dev server - "http://localhost:4321" # Astro dev server
- "https://victorvargas.dev" # Producción (cuando exista) - "https://victorvargas.dev" # Production (when it exists)
rate_limit: rate_limit:
requests_per_minute: 30 # Por IP requests_per_minute: 30 # Per IP
burst: 5 burst: 5
# Providers LLM (al menos uno configurado) # LLM providers (at least one configured)
providers: providers:
# === Ollama (recomendado para desarrollo) === # === Ollama (recommended for development) ===
- name: ollama-local - name: ollama-local
type: ollama type: ollama
model: qwen2.5:1.5b # Modelo pequeño para Q&A model: qwen2.5:1.5b # Small model for Q&A
endpoint: http://localhost:11434 endpoint: http://localhost:11434
default: true default: true
# === llama.cpp directo (GGUF) === # === llama.cpp direct (GGUF) ===
- name: llamacpp-local - name: llamacpp-local
type: llamacpp type: llamacpp
model_path: ${RONY_MODELS_PATH}/qwen2.5-1.5b-instruct-q5_k_m.gguf model_path: ${RONY_MODELS_PATH}/qwen2.5-1.5b-instruct-q5_k_m.gguf
context_size: 4096 context_size: 4096
n_gpu_layers: 999 n_gpu_layers: 999
# === Anthropic (si quieres calidad > privacidad) === # === Anthropic (if you want quality > privacy) ===
- name: anthropic-api - name: anthropic-api
type: anthropic type: anthropic
model: claude-haiku-4 # Modelo barato model: claude-haiku-4 # Cheap model
api_key_env: ANTHROPIC_API_KEY api_key_env: ANTHROPIC_API_KEY
# RAG: cómo se indexan los proyectos # RAG: how projects are indexed
rag: rag:
enabled: true enabled: true
data_path: ./data/projects # Directorio con .md data_path: ./data/projects # Directory with .md
chunk_size: 500 # caracteres por chunk chunk_size: 500 # characters per chunk
chunk_overlap: 50 chunk_overlap: 50
embedding_provider: ollama # o llamacpp embedding_provider: ollama # or llamacpp
embedding_model: nomic-embed-text embedding_model: nomic-embed-text
vector_db_path: ./chroma # Persistencia local vector_db_path: ./chroma # Local persistence
top_k: 5 # Documentos a recuperar por query top_k: 5 # Documents to retrieve per query
rerank: false # Phase 2 rerank: false # Phase 2
# Persona: quién es el bot # Persona: who the bot is
persona: persona:
name: "Rony Chat Bot" name: "Rony Chat Bot"
tone: "Profesional, conocedor, amable" tone: "Professional, knowledgeable, friendly"
language: "Español" language: "English"
constraints: constraints:
- "Solo responder sobre Victor y sus proyectos" - "Only answer about Victor and his projects"
- "Si no sabes, decir 'No tengo esa información'" - "If you don't know, say 'I don't have that information'"
- "Ser conciso pero informativo" - "Be concise but informative"
- "Usar formato markdown para listas y código" - "Use markdown format for lists and code"
intro: "¡Hola! Soy Rony, el asistente virtual de Victor Hugo Vargas. Pregúntame sobre sus proyectos, skills o experiencia." intro: "Hi! I'm Rony, Victor Hugo Vargas's virtual assistant. Ask me about his projects, skills or experience."
# System prompt base (concatenado con el contenido RAG) # Base system prompt (concatenated with RAG content)
system_prompt: | system_prompt: |
Eres Rony Chat Bot, el asistente virtual de Victor Hugo Vargas, un ingeniero de software mexicano. You are Rony Chat Bot, the virtual assistant of Victor Hugo Vargas, a Mexican software engineer.
Tu trabajo es responder preguntas sobre: Your job is to answer questions about:
- Los proyectos de Victor (ver archivos en data/projects/) - Victor's projects (see files in data/projects/)
- Su experiencia y skills técnicas - His experience and technical skills
- Su enfoque de trabajo - His work approach
Responde en español, con tono profesional pero accesible. Respond in English, with professional but accessible tone.
Si te preguntan algo que no está en tu contexto, dilo honestamente. If you're asked something not in your context, say it honestly.
Formato recomendado: Recommended format:
- Usa markdown para listas, código, y énfasis - Use markdown for lists, code, and emphasis
- Sé conciso (máximo 2-3 párrafos por respuesta) - Be concise (max 2-3 paragraphs per response)
- Incluye links a repos cuando sea relevante - Include links to repos when relevant
# Logging # Logging
logging: logging:

View file

@ -0,0 +1,52 @@
# Proyectos del Portfolio
> 🌐 **Idioma:** [English](README.md) | [Español](README.es.md)
Coloca aquí un archivo `.md` por cada proyecto que quieras que el bot pueda responder.
## Convención de nombres
- Un archivo por proyecto: `nombre-del-proyecto.md`
- Nombre en kebab-case (minúsculas con guiones)
- Ejemplo: `rony-tui.md`, `rony-llm-agent.md`, `portfolio-astro.md`
## Frontmatter (opcional pero recomendado)
```markdown
---
title: "Rony TUI"
date: 2026-06
status: "active" # active | archived | wip
tags: ["go", "ai", "cli"]
repo: "https://github.com/VictorVargas/rony-harness"
demo: "https://..." # opcional
---
# Rony TUI
AI agent harness para desarrollo de software...
```
## Cómo se procesan
1. El bot escanea este directorio al arrancar
2. Cada `.md` se divide en chunks de ~500 caracteres
3. Cada chunk se convierte a embedding con Ollama
4. Los embeddings se guardan en ChromaDB
5. Cuando alguien pregunta, se buscan los top-5 chunks más relevantes
6. Esos chunks se inyectan al contexto del LLM
## Re-indexar
Si modificas los `.md`, ejecuta:
```bash
./bin/chat-bot reindex
```
Esto reconstruye ChromaDB desde cero.
## Ejemplo de proyecto
Ver [`example-project.md`](./example-project.md) para una plantilla.

View file

@ -1,49 +1,49 @@
# Proyectos del Portfolio # Portfolio Projects
Coloca aquí un archivo `.md` por cada proyecto que quieras que el bot pueda responder. Place a `.md` file here for each project you want the bot to be able to answer about.
## Convención de nombres ## File naming convention
- Un archivo por proyecto: `nombre-del-proyecto.md` - One file per project: `project-name.md`
- Nombre en kebab-case (minúsculas con guiones) - Name in kebab-case (lowercase with hyphens)
- Ejemplo: `rony-tui.md`, `rony-llm-agent.md`, `portfolio-astro.md` - Example: `rony-harness.md`, `rony-llm-agent.md`, `portfolio-astro.md`
## Frontmatter (opcional pero recomendado) ## Frontmatter (optional but recommended)
```markdown ```markdown
--- ---
title: "Rony TUI" title: "Rony Harness"
date: 2026-06 date: 2026-06
status: "active" # active | archived | wip status: "active" # active | archived | wip
tags: ["go", "ai", "cli"] tags: ["go", "ai", "cli"]
repo: "https://github.com/VictorVargas/rony-harness" repo: "https://github.com/VictorVargas/rony-harness"
demo: "https://..." # opcional demo: "https://..." # optional
--- ---
# Rony TUI # Rony Harness
AI agent harness para desarrollo de software... AI agent harness for software development...
``` ```
## Cómo se procesan ## How they're processed
1. El bot escanea este directorio al arrancar 1. The bot scans this directory on startup
2. Cada `.md` se divide en chunks de ~500 caracteres 2. Each `.md` is split into chunks of ~500 characters
3. Cada chunk se convierte a embedding con Ollama 3. Each chunk is converted to embedding with Ollama
4. Los embeddings se guardan en ChromaDB 4. Embeddings are stored in ChromaDB
5. Cuando alguien pregunta, se buscan los top-5 chunks más relevantes 5. When someone asks a question, the top-5 most relevant chunks are searched
6. Esos chunks se inyectan al contexto del LLM 6. Those chunks are injected into the LLM context
## Re-indexar ## Re-index
Si modificas los `.md`, ejecuta: If you modify the `.md` files, run:
```bash ```bash
./bin/chat-bot reindex ./bin/chat-bot reindex
``` ```
Esto reconstruye ChromaDB desde cero. This rebuilds ChromaDB from scratch.
## Ejemplo de proyecto ## Project example
Ver [`example-project.md`](./example-project.md) para una plantilla. See [`example-project.md`](./example-project.md) for a template.

View file

@ -0,0 +1,45 @@
---
title: "Proyecto de Ejemplo"
date: 2026-06
status: "active"
tags: ["ejemplo", "plantilla"]
---
> 🌐 **Idioma:** [English](example-project.md) | [Español](example-project.es.md)
repo: ""
demo: ""
---
# Proyecto de Ejemplo
> 🌐 **Language:** [English](example-project.md) | [Español](example-project.es.md)
Este es un template. Reemplaza con la información real de tu proyecto.
## Descripción
Qué hace el proyecto, en 1-2 párrafos. Evita jerga innecesaria.
## Stack técnico
- **Lenguaje:** Go 1.26
- **Framework:** Ninguno (stdlib)
- **Base de datos:** SQLite
- **Deployment:** Fly.io
## Features principales
1. Feature uno — descripción breve
2. Feature dos — descripción breve
3. Feature tres — descripción breve
## Aprendizajes
Qué aprendiste, qué challenges tuviste, qué harías diferente.
## Links
- Repo: github.com/VictorVargas/proyecto
- Demo: proyecto.example.com
- Docs: docs.proyecto.example.com

View file

@ -1,39 +1,39 @@
--- ---
title: "Proyecto de Ejemplo" title: "Example Project"
date: 2026-06 date: 2026-06
status: "active" status: "active"
tags: ["ejemplo", "plantilla"] tags: ["example", "template"]
repo: "" repo: ""
demo: "" demo: ""
--- ---
# Proyecto de Ejemplo # Example Project
Este es un template. Reemplaza con la información real de tu proyecto. This is a template. Replace with the real information about your project.
## Descripción ## Description
Qué hace el proyecto, en 1-2 párrafos. Evita jerga innecesaria. What the project does, in 1-2 paragraphs. Avoid unnecessary jargon.
## Stack técnico ## Tech stack
- **Lenguaje:** Go 1.26 - **Language:** Go 1.26
- **Framework:** Ninguno (stdlib) - **Framework:** None (stdlib)
- **Base de datos:** SQLite - **Database:** SQLite
- **Deployment:** Fly.io - **Deployment:** Fly.io
## Features principales ## Main features
1. Feature uno — descripción breve 1. Feature one — brief description
2. Feature dos — descripción breve 2. Feature two — brief description
3. Feature tres — descripción breve 3. Feature three — brief description
## Aprendizajes ## Learnings
Qué aprendiste, qué challenges tuviste, qué harías diferente. What you learned, what challenges you faced, what you'd do differently.
## Links ## Links
- Repo: github.com/VictorVargas/proyecto - Repo: github.com/VictorVargas/project
- Demo: proyecto.example.com - Demo: project.example.com
- Docs: docs.proyecto.example.com - Docs: docs.project.example.com

1044
docs/architecture.es.md Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,93 +1,96 @@
# 📋 Rony Chat Bot — Technical Design Document # 📋 Rony Chat Bot — Technical Design Document
**Versión:** 1.0 **Version:** 1.0
**Autor:** Victor Hugo Vargas **Author:** Victor Hugo Vargas
**Fecha:** 2026-06-28 **Date:** 2026-06-28
**Estado:** Especificación completa para implementación **Status:** Complete specification for implementation
**Path:** `rony-chat-bot/docs/architecture.md` **Path:** `rony-chat-bot/docs/architecture.md`
> 📚 **Workspace:** Este proyecto es parte del workspace `Rony/`. Ver [`../README.md`](../../README.md). > 🌐 **Language:** [English](./architecture.md) | [Español](./architecture.es.md)
> >
> 🔑 **Depende de:** [`rony-llm-agent`](https://github.com/VictorVargas/rony-llm-agent) — librería core que provee agent loop, LLM clients, RAG, persona system. > 📚 **Workspace:** This project is part of the `Rony/` workspace. See [`../README.md`](../../README.md).
> >
> 📐 **Metodología:** Este proyecto sigue el enfoque **SDD + DDD + Hexagonal Architecture**. Los Requisitos Funcionales se numeran como `CRF-XXX`. Ver [`../../METHODOLOGY.md`](../../METHODOLOGY.md). > 🔑 **Depends on:** [`rony-llm-agent`](https://github.com/VictorVargas/rony-llm-agent) — core library that provides agent loop, LLM clients, RAG, persona system.
>
> 📐 **Methodology:** This project follows the **SDD + DDD + Hexagonal Architecture** approach. Functional Requirements are numbered as `CRF-XXX`. See [`../../METHODOLOGY.md`](../../METHODOLOGY.md).
--- ---
## 🎯 1. Visión del Proyecto ## 🎯 1. Project Vision
### 1.1 ¿Qué es Chat-Bot? ### 1.1 What is Chat-Bot?
Un **chatbot HTTP** que responde preguntas sobre Victor Hugo Vargas y sus proyectos. Usa **RAG (Retrieval-Augmented Generation)** sobre archivos markdown que describen cada proyecto, y un LLM local (o cloud) para generar respuestas. An **HTTP chatbot** that answers questions about Victor Hugo Vargas and his projects. Uses **RAG (Retrieval-Augmented Generation)** over markdown files describing each project, and a local LLM (or cloud) to generate responses.
### 1.2 Caso de uso primario ### 1.2 Primary use case
Victor tiene un portfolio web (Astro + React). En el sitio hay un widget de chat donde visitantes pueden preguntar: Victor has a portfolio website (Astro + React). On the site there's a chat widget where visitors can ask:
- "¿Qué proyectos ha hecho Victor?"
- "¿Cuál es su experiencia con Go?"
- "¿Cómo funciona Rony TUI?"
- "¿Victor ha trabajado con PostgreSQL?"
El bot responde con información precisa extraída de los archivos markdown de proyectos + bio + skills. - "What projects has Victor done?"
- "What's his experience with Go?"
- "How does Rony Harness work?"
- "Has Victor worked with PostgreSQL?"
### 1.3 Casos de uso secundarios (futuro) The bot responds with accurate information extracted from the projects' markdown files + bio + skills.
- **Adaptación a clientes:** El mismo bot, con otra data y otra persona, sirve para concesionarios, restaurantes, etc. ### 1.3 Secondary use cases (future)
- **Standalone CLI:** `./chat-bot ask "¿qué sabes de X?"` para uso desde terminal.
- **Slack/Discord bot:** Wrapper que consume el HTTP API.
### 1.4 Filosofía - **Client adaptation:** The same bot, with other data and another persona, serves car dealerships, restaurants, etc.
- **Standalone CLI:** `./chat-bot ask "what do you know about X?"` for terminal use.
- **Slack/Discord bot:** Wrapper that consumes the HTTP API.
- **Self-hosted por defecto** — funciona 100% local con Ollama + modelos 1-3B ### 1.4 Philosophy
- **Cloud opcional** — si se necesita más calidad, swap a Anthropic API
- **Portable** — fácil de fork/customizar para otros contextos - **Self-hosted by default** — works 100% local with Ollama + 1-3B models
- **Streaming** — respuestas token-por-token con SSE (no espera a respuesta completa) - **Cloud optional** — if you need more quality, swap to Anthropic API
- **Reutiliza `rony-llm-agent`** — no reinventar el agent loop - **Portable** — easy to fork/customize for other contexts
- **Streaming** — token-by-token responses with SSE (no waiting for complete response)
- **Reuses `rony-llm-agent`** — doesn't reinvent the agent loop
--- ---
## 🏗️ 2. Arquitectura ## 🏗️ 2. Architecture
### 2.1 Vista general ### 2.1 Overview
``` ```
┌─────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────┐
│ Browser (Astro site) │ │ Browser (Astro site) │
│ ↓ HTTP POST /api/chat │ │ ↓ HTTP POST /api/chat │
│ Astro SSR (proxy) ←────────── Sirve portfolio + proxy chat │ │ Astro SSR (proxy) ←────────── Serves portfolio + proxy chat │
│ ↓ HTTP POST /api/chat │ │ ↓ HTTP POST /api/chat │
│ Chat-Bot HTTP server (:7331) │ │ Chat-Bot HTTP server (:7331) │
│ ↓ │ │ ↓ │
│ Agent loop (rony-llm-agent) │ │ Agent loop (rony-llm-agent) │
│ ↓ │ │ ↓ │
│ RAG retrieval → ChromaDB sobre data/projects/*.md │ │ RAG retrieval → ChromaDB over data/projects/*.md │
│ ↓ │ │ ↓ │
│ LLM (Ollama local / Anthropic cloud) │ │ LLM (Ollama local / Anthropic cloud) │
└─────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────┘
``` ```
### 2.2 Componentes principales ### 2.2 Main components
| Componente | Path | Responsabilidad | | Component | Path | Responsibility |
|---|---|---| |---|---|---|
| **HTTP server** | `internal/server/` | Gin/chi handlers, SSE streaming | | **HTTP server** | `internal/server/` | Gin/chi handlers, SSE streaming |
| **Agent runner** | `internal/agent/` | Wrapper sobre `rony-llm-agent` con config específica | | **Agent runner** | `internal/agent/` | Wrapper over `rony-llm-agent` with specific config |
| **Portfolio loader** | `internal/portfolio/` | Lee `data/projects/*.md`, indexa en ChromaDB | | **Portfolio loader** | `internal/portfolio/` | Reads `data/projects/*.md`, indexes in ChromaDB |
| **Persona** | `internal/persona/` | Carga persona desde `configs/portfolio-bot.yaml` | | **Persona** | `internal/persona/` | Loads persona from `configs/portfolio-bot.yaml` |
| **CLI** | `cm./rony-chat-bot/` | Comandos: `serve`, `reindex`, `ask`, `version` | | **CLI** | `cmd/chat-bot/` | Commands: `serve`, `reindex`, `ask`, `version` |
### 2.3 Stack tecnológico ### 2.3 Tech stack
| Capa | Tecnología | Razón | | Layer | Technology | Reason |
|---|---|---| |---|---|---|
| **Lenguaje** | Go 1.26+ | Mismo que `harness`, aprovechar `os.Root`, `iter.Seq` | | **Language** | Go 1.26+ | Same as rony-harness, leverage `os.Root`, `iter.Seq` |
| **HTTP router** | `net/http` + `chi` | Stdlib + chi para middleware (CORS, logging) | | **HTTP router** | `net/http` + `chi` | Stdlib + chi for middleware (CORS, logging) |
| **SSE** | `net/http` Flusher | Stdlib es suficiente, no necesita librería externa | | **SSE** | `net/http` Flusher | Stdlib is enough, no external library needed |
| **Config** | `gopkg.in/yaml.v3` | Mismo que harness | | **Config** | `gopkg.in/yaml.v3` | Same as harness |
| **RAG backend** | ChromaDB embedded via `chroma-go` | Self-hosted, simple API | | **RAG backend** | ChromaDB embedded via `chroma-go` | Self-hosted, simple API |
| **Embeddings** | Ollama (nomic-embed-text) | Local, gratis, buena calidad | | **Embeddings** | Ollama (nomic-embed-text) | Local, free, good quality |
| **LLM** | Ollama (qwen2.5:1.5b) o llama.cpp | Self-hosted por defecto | | **LLM** | Ollama (qwen2.5:1.5b) or llama.cpp | Self-hosted by default |
| **Tests** | stdlib + testify | Consistencia con el resto | | **Tests** | stdlib + testify | Consistency with the rest |
--- ---
@ -95,13 +98,13 @@ El bot responde con información precisa extraída de los archivos markdown de p
### 3.1 Endpoints ### 3.1 Endpoints
#### `POST /api/chat` — Chat con streaming SSE #### `POST /api/chat` — Chat with SSE streaming
**Request:** **Request:**
```json ```json
{ {
"messages": [ "messages": [
{"role": "user", "content": "¿Qué proyectos tiene Victor?"} {"role": "user", "content": "What projects does Victor have?"}
], ],
"stream": true "stream": true
} }
@ -112,29 +115,29 @@ El bot responde con información precisa extraída de los archivos markdown de p
data: {"type":"start","conversation_id":"abc123"} data: {"type":"start","conversation_id":"abc123"}
data: {"type":"chunk","content":"Victor"} data: {"type":"chunk","content":"Victor"}
data: {"type":"chunk","content":" tiene"} data: {"type":"chunk","content":" has"}
data: {"type":"chunk","content":" varios"} data: {"type":"chunk","content":" several"}
data: {"type":"chunk","content":" proyectos"} data: {"type":"chunk","content":" projects"}
data: {"type":"sources","documents":["rony-tui.md","rony-llm-agent.md"]} data: {"type":"sources","documents":["rony-harness.md","rony-llm-agent.md"]}
data: {"type":"done","usage":{"input_tokens":245,"output_tokens":38}} data: {"type":"done","usage":{"input_tokens":245,"output_tokens":38}}
``` ```
**Sin streaming** (`"stream": false`): **Without streaming** (`"stream": false`):
```json ```json
{ {
"content": "Victor tiene varios proyectos...", "content": "Victor has several projects...",
"sources": ["rony-tui.md", "rony-llm-agent.md"], "sources": ["rony-harness.md", "rony-llm-agent.md"],
"usage": {"input_tokens": 245, "output_tokens": 38} "usage": {"input_tokens": 245, "output_tokens": 38}
} }
``` ```
#### `POST /api/reindex` — Re-indexar portfolio #### `POST /api/reindex` — Re-index portfolio
Útil cuando se modifican archivos en `data/projects/`. Useful when files in `data/projects/` are modified.
**Request:** vacío **Request:** empty
**Response:** **Response:**
```json ```json
{ {
@ -159,14 +162,14 @@ data: {"type":"done","usage":{"input_tokens":245,"output_tokens":38}}
} }
``` ```
#### `GET /api/info`Metadata del bot #### `GET /api/info`Bot metadata
```json ```json
{ {
"name": "Asistente de Victor Hugo Vargas", "name": "Rony Chat Bot",
"model": "qwen2.5:1.5b", "model": "qwen2.5:1.5b",
"persona": "...", "persona": "...",
"topics": ["proyectos", "experiencia", "skills técnicas"] "topics": ["projects", "experience", "technical skills"]
} }
``` ```
@ -184,7 +187,7 @@ import (
) )
func (s *Server) handleChat(w http.ResponseWriter, r *http.Request) { func (s *Server) handleChat(w http.ResponseWriter, r *http.Request) {
// Headers SSE // SSE headers
w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive") w.Header().Set("Connection", "keep-alive")
@ -192,7 +195,7 @@ func (s *Server) handleChat(w http.ResponseWriter, r *http.Request) {
flusher, ok := w.(http.Flusher) flusher, ok := w.(http.Flusher)
if !ok { if !ok {
http.Error(w, "SSE no soportado", http.StatusInternalServerError) http.Error(w, "SSE not supported", http.StatusInternalServerError)
return return
} }
@ -208,7 +211,7 @@ func (s *Server) handleChat(w http.ResponseWriter, r *http.Request) {
"conversation_id": generateConvID(), "conversation_id": generateConvID(),
}) })
// Run agent con streaming // Run agent with streaming
sources := []string{} sources := []string{}
for chunk, err := range s.agent.RunStream(r.Context(), req.Messages) { for chunk, err := range s.agent.RunStream(r.Context(), req.Messages) {
if err != nil { if err != nil {
@ -246,7 +249,7 @@ package server
func (s *Server) loggingMiddleware(next http.Handler) http.Handler { func (s *Server) loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now() start := time.Now()
// Wrap response writer para capturar status // Wrap response writer to capture status
rw := &statusRecorder{ResponseWriter: w, status: 200} rw := &statusRecorder{ResponseWriter: w, status: 200}
next.ServeHTTP(rw, r) next.ServeHTTP(rw, r)
@ -295,7 +298,7 @@ func (s *Server) rateLimitMiddleware(next http.Handler) http.Handler {
## 🧠 4. RAG (Retrieval-Augmented Generation) ## 🧠 4. RAG (Retrieval-Augmented Generation)
### 4.1 Pipeline de indexación ### 4.1 Indexing pipeline
``` ```
data/projects/*.md data/projects/*.md
@ -309,26 +312,26 @@ Vectors [][]float32
Indexed corpus Indexed corpus
``` ```
**Cuándo se ejecuta:** **When it runs:**
- Al arrancar el bot (si `--reindex-on-start` flag) - On bot startup (if `--reindex-on-start` flag)
- Manualmente: `./chat-bot reindex` - Manually: `./chat-bot reindex`
- Vía HTTP: `POST /api/reindex` - Via HTTP: `POST /api/reindex`
### 4.2 Pipeline de retrieval ### 4.2 Retrieval pipeline
``` ```
User query "¿qué proyectos tiene Victor?" User query "what projects does Victor have?"
↓ (embed query) ↓ (embed query)
Query vector Query vector
↓ (cosine similarity search en ChromaDB, top_k=5) ↓ (cosine similarity search in ChromaDB, top_k=5)
Top 5 chunks relevantes Top 5 relevant chunks
↓ (format as context block) ↓ (format as context block)
System prompt += chunks relevantes System prompt += relevant chunks
↓ (send to LLM) ↓ (send to LLM)
LLM generates answer LLM generates answer
``` ```
### 4.3 Implementación ### 4.3 Implementation
```go ```go
// internal/portfolio/indexer.go // internal/portfolio/indexer.go
@ -404,8 +407,8 @@ func (i *Indexer) indexFile(ctx context.Context, path string) (int, error) {
} }
func splitIntoChunks(text string, size, overlap int) []string { func splitIntoChunks(text string, size, overlap int) []string {
// Implementación simple: split por tamaño con overlap // Simple implementation: split by size with overlap
// Versión production usa tokenizer-aware chunking // Production version uses tokenizer-aware chunking
var chunks []string var chunks []string
for i := 0; i < len(text); i += size - overlap { for i := 0; i < len(text); i += size - overlap {
end := i + size end := i + size
@ -418,7 +421,7 @@ func splitIntoChunks(text string, size, overlap int) []string {
} }
``` ```
### 4.4 Retrieval en el agent loop ### 4.4 Retrieval in the agent loop
```go ```go
// internal/agent/runner.go // internal/agent/runner.go
@ -437,9 +440,9 @@ func (r *Runner) buildSystemPrompt(ctx context.Context, query string) (string, e
// 3. Format as context // 3. Format as context
var contextBlock strings.Builder var contextBlock strings.Builder
contextBlock.WriteString(basePrompt) contextBlock.WriteString(basePrompt)
contextBlock.WriteString("\n\n## Contexto relevante\n\n") contextBlock.WriteString("\n\n## Relevant context\n\n")
for idx, frag := range fragments { for idx, frag := range fragments {
contextBlock.WriteString(fmt.Sprintf("### Fuente: %s\n%s\n\n", contextBlock.WriteString(fmt.Sprintf("### Source: %s\n%s\n\n",
frag.Metadata["source_file"], frag.Content)) frag.Metadata["source_file"], frag.Content))
} }
@ -471,31 +474,30 @@ func (r *Runner) RunStream(ctx context.Context, messages []llm.Message) iter.Seq
--- ---
## 🌐 5. Integración con Astro (Portfolio) ## 🌐 5. Integration with Astro (Portfolio)
### 5.1 Patrón recomendado: Astro proxy ### 5.1 Recommended pattern: Astro proxy
``` ```
[Browser] ←→ [Astro SSR :4321] ←→ [Chat-Bot :7331] [Browser] ←→ [Astro SSR :4321] ←→ [Chat-Bot :7331]
``` ```
**Por qué proxy y no llamada directa del browser al chat-bot:** **Why proxy and not direct browser call to chat-bot:**
- ✅ Single domain (no CORS) - ✅ Single domain (no CORS)
- ✅ Astro maneja auth/sesión si se necesita - ✅ Astro handles auth/session if needed
- ✅ Puede haber rate limiting centralizado en Astro - ✅ There can be centralized rate limiting in Astro
- ✅ El chat-bot queda en red privada (no expuesto a internet directamente) - ✅ The chat-bot stays on private network (not exposed to internet directly)
### 5.2 Astro: API route del proxy ### 5.2 Astro: API route of the proxy
```typescript ```typescript
// portfolio/src/pages/api/chat.ts // portfolio/src/pages/api/chat.ts
import type { APIRoute } from 'astro'; import type { APIRoute } from 'astro';
const CHAT_BOT_URL = process.env.CHAT_BOT_URL || 'http://localhost:7331'; const CHAT_BOT_URL = import.meta.env.CHAT_BOT_URL || 'http://localhost:7331';
export const POST: APIRoute = async ({ request }) => { export const POST: APIRoute = async ({ request }) => {
const body = await request.json(); const body = await request.json();
const resp = await fetch(`${CHAT_BOT_URL}/api/chat`, { const resp = await fetch(`${CHAT_BOT_URL}/api/chat`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@ -506,7 +508,7 @@ export const POST: APIRoute = async ({ request }) => {
return new Response('Chat bot error', { status: resp.status }); return new Response('Chat bot error', { status: resp.status });
} }
// Stream SSE de vuelta al browser // Stream SSE back to browser
return new Response(resp.body, { return new Response(resp.body, {
status: 200, status: 200,
headers: { headers: {
@ -518,7 +520,7 @@ export const POST: APIRoute = async ({ request }) => {
}; };
``` ```
### 5.3 React: Componente del chat ### 5.3 React: Chat component
```tsx ```tsx
// portfolio/src/components/Chat.tsx // portfolio/src/components/Chat.tsx
@ -543,7 +545,7 @@ export default function Chat() {
setInput(''); setInput('');
setStreaming(true); setStreaming(true);
// Placeholder para streaming // Placeholder for streaming
const assistantMsg: Message = { role: 'assistant', content: '' }; const assistantMsg: Message = { role: 'assistant', content: '' };
setMessages(prev => [...prev, assistantMsg]); setMessages(prev => [...prev, assistantMsg]);
@ -611,7 +613,7 @@ export default function Chat() {
value={input} value={input}
onChange={e => setInput(e.target.value)} onChange={e => setInput(e.target.value)}
onKeyDown={e => e.key === 'Enter' && send()} onKeyDown={e => e.key === 'Enter' && send()}
placeholder="Pregunta sobre Victor..." placeholder="Ask about Victor..."
disabled={streaming} disabled={streaming}
/> />
{streaming ? ( {streaming ? (
@ -627,39 +629,39 @@ export default function Chat() {
--- ---
## 🤖 6. Self-hosting con Ollama ## 🤖 6. Self-hosting with Ollama
### 6.1 Setup ### 6.1 Setup
```bash ```bash
# 1. Instalar Ollama # 1. Install Ollama
curl -fsSL https://ollama.com/install.sh | sh curl -fsSL https://ollama.com/install.sh | sh
# 2. Descargar modelo de chat # 2. Download chat model
ollama pull qwen2.5:1.5b ollama pull qwen2.5:1.5b
# 3. Descargar modelo de embeddings # 3. Download embeddings model
ollama pull nomic-embed-text ollama pull nomic-embed-text
# 4. Verificar # 4. Verify
ollama list ollama list
``` ```
### 6.2 Configuración por defecto ### 6.2 Default configuration
`configs/portfolio-bot.yaml` ya viene con Ollama como default. Solo necesitas: `configs/portfolio-bot.yaml` already comes with Ollama as default. You only need:
```bash ```bash
# Asegurar que Ollama está corriendo # Make sure Ollama is running
ollama serve ollama serve
# Arrancar el bot # Start the bot
./bin/chat-bot serve ./bin/chat-bot serve
``` ```
### 6.3 Alternativa: llama.cpp directo ### 6.3 Alternative: llama.cpp direct
Para más control o si Ollama no funciona en tu setup: For more control or if Ollama doesn't work in your setup:
```yaml ```yaml
providers: providers:
@ -667,42 +669,42 @@ providers:
type: llamacpp type: llamacpp
model_path: ${RONY_MODELS_PATH}/qwen2.5-1.5b-instruct-q5_k_m.gguf model_path: ${RONY_MODELS_PATH}/qwen2.5-1.5b-instruct-q5_k_m.gguf
context_size: 4096 context_size: 4096
n_gpu_layers: 999 # offload todo a GPU n_gpu_layers: 999 # offload all to GPU
default: true default: true
``` ```
El adapter `llamacpp` se importa desde `rony-llm-agent/pkg/llm/providers/llamacpp` y se compila contra `llama.cpp` vía CGO o binario externo. The `llamacpp` adapter is imported from `rony-llm-agent/pkg/llm/providers/llamacpp` and is compiled against `llama.cpp` via CGO or external binary.
--- ---
## 📦 7. CLI del bot ## 📦 7. Bot CLI
### 7.1 Comandos ### 7.1 Commands
```bash ```bash
# Arrancar servidor HTTP # Start HTTP server
chat-bot serve [--port 7331] [--host 0.0.0.0] [--reindex-on-start] chat-bot serve [--port 7331] [--host 0.0.0.0] [--reindex-on-start]
# Re-indexar portfolio (lee data/projects/*.md → ChromaDB) # Re-index portfolio (reads data/projects/*.md → ChromaDB)
chat-bot reindex chat-bot reindex
# Pregunta única (sin servidor, útil para tests) # Single question (no server, useful for tests)
chat-bot ask "¿Qué proyectos tiene Victor?" [--no-rag] chat-bot ask "What projects does Victor have?" [--no-rag]
# Validar config # Validate config
chat-bot config validate chat-bot config validate
# Health check (útil para monitoring) # Health check (useful for monitoring)
chat-bot health chat-bot health
# Versión # Version
chat-bot version chat-bot version
``` ```
### 7.2 Implementación con Cobra ### 7.2 Implementation with Cobra
```go ```go
// cm./rony-chat-bot/main.go // cmd/chat-bot/main.go
package main package main
import ( import (
@ -756,10 +758,10 @@ func serveCmd() *cobra.Command {
## 🚀 8. Deployment ## 🚀 8. Deployment
### 8.1 Recomendación: Self-hosted en VPS ### 8.1 Recommendation: Self-hosted on VPS
```bash ```bash
# 1. Instalar dependencias # 1. Install dependencies
sudo apt install golang-go ollama sudo apt install golang-go ollama
ollama pull qwen2.5:1.5b ollama pull qwen2.5:1.5b
ollama pull nomic-embed-text ollama pull nomic-embed-text
@ -800,7 +802,7 @@ chat.victorvargas.dev {
### 8.3 Monitoring ### 8.3 Monitoring
```bash ```bash
# Health check periódico # Health check periodic
curl -s http://localhost:7331/api/health | jq curl -s http://localhost:7331/api/health | jq
# Logs # Logs
@ -821,7 +823,7 @@ func TestHandleChat_ValidRequest(t *testing.T) {
s := newTestServer(t) s := newTestServer(t)
req := httptest.NewRequest("POST", "/api/chat", strings.NewReader(`{ req := httptest.NewRequest("POST", "/api/chat", strings.NewReader(`{
"messages": [{"role": "user", "content": "hola"}] "messages": [{"role": "user", "content": "hello"}]
}`)) }`))
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
@ -838,31 +840,31 @@ func TestHandleChat_RateLimit(t *testing.T) {
}) })
// First request OK // First request OK
req1 := newChatRequest("hola") req1 := newChatRequest("hello")
w1 := httptest.NewRecorder() w1 := httptest.NewRecorder()
s.handleChat(w1, req1) s.handleChat(w1, req1)
assert.Equal(t, 200, w1.Code) assert.Equal(t, 200, w1.Code)
// Second request denied // Second request denied
req2 := newChatRequest("hola de nuevo") req2 := newChatRequest("hello again")
w2 := httptest.NewRecorder() w2 := httptest.NewRecorder()
s.handleChat(w2, req2) s.handleChat(w2, req2)
assert.Equal(t, 429, w2.Code) assert.Equal(t, 429, w2.Code)
} }
``` ```
### 9.2 Integration tests con mock LLM ### 9.2 Integration tests with mock LLM
```go ```go
// internal/agent/runner_test.go // internal/agent/runner_test.go
func TestRunner_RAGContextIsInjected(t *testing.T) { func TestRunner_RAGContextIsInjected(t *testing.T) {
mockLLM := mock.New(mock.Responses{ mockLLM := mock.New(mock.Responses{
{Match: "proyectos", Response: "Victor tiene varios proyectos..."}, {Match: "projects", Response: "Victor has several projects..."},
}) })
memory := newMockMemoryWithDocs(t, []rag.Fragment{ memory := newMockMemoryWithDocs(t, []rag.Fragment{
{Content: "Rony TUI: AI agent harness...", ProjectID: "rony-tui"}, {Content: "Rony Harness: AI agent harness...", ProjectID: "rony-harness"},
{Content: "rony-llm-agent: librería Go...", ProjectID: "rony-llm-agent"}, {Content: "go-llm-agent: Go library...", ProjectID: "rony-llm-agent"},
}) })
runner := agent.NewRunner(agent.Config{ runner := agent.NewRunner(agent.Config{
@ -872,36 +874,36 @@ func TestRunner_RAGContextIsInjected(t *testing.T) {
}) })
resp, _ := runner.Run(context.Background(), []llm.Message{ resp, _ := runner.Run(context.Background(), []llm.Message{
{Role: llm.RoleUser, Content: "¿qué proyectos tiene Victor?"}, {Role: llm.RoleUser, Content: "what projects does Victor have?"},
}) })
// Verify LLM received context chunks in system prompt // Verify LLM received context chunks in system prompt
lastReq := mockLLM.LastRequest() lastReq := mockLLM.LastRequest()
assert.Contains(t, lastReq.Messages[0].Content, "Rony TUI") assert.Contains(t, lastReq.Messages[0].Content, "Rony Harness")
assert.Contains(t, lastReq.Messages[0].Content, "rony-llm-agent") assert.Contains(t, lastReq.Messages[0].Content, "go-llm-agent")
} }
``` ```
### 9.3 E2E test con Astro ### 9.3 E2E test with Astro
```bash ```bash
# 1. Arrancar chat-bot en :7331 # 1. Start chat-bot on :7331
./bin/chat-bot serve & ./bin/chat-bot serve &
# 2. Arrancar Astro en :4321 # 2. Start Astro on :4321
cd ../portfolio && npm run dev & cd ../portfolio && npm run dev &
# 3. Hacer request al proxy de Astro # 3. Make request to Astro's proxy
curl -X POST http://localhost:4321/api/chat \ curl -X POST http://localhost:4321/api/chat \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"hola"}]}' -d '{"messages":[{"role":"user","content":"hello"}]}'
# 4. Verificar SSE stream # 4. Verify SSE stream
``` ```
--- ---
## 📂 10. Estructura del Proyecto ## 📂 10. Project Structure
``` ```
chat-bot/ chat-bot/
@ -918,21 +920,21 @@ chat-bot/
│ │ ├── middleware.go # logging, CORS, rate limit │ │ ├── middleware.go # logging, CORS, rate limit
│ │ └── sse.go # SSE helpers │ │ └── sse.go # SSE helpers
│ │ │ │
│ ├── agent/ # Wrapper sobre rony-llm-agent │ ├── agent/ # Wrapper over rony-llm-agent
│ │ ├── runner.go # RunStream con RAG injection │ │ ├── runner.go # RunStream with RAG injection
│ │ └── prompts.go # System prompt builder │ │ └── prompts.go # System prompt builder
│ │ │ │
│ ├── portfolio/ # Data loader │ ├── portfolio/ # Data loader
│ │ ├── indexer.go # Lee .md, chunks, embed, store │ │ ├── indexer.go # Reads .md, chunks, embed, store
│ │ ├── retriever.go # Query → top-k chunks │ │ ├── retriever.go # Query → top-k chunks
│ │ └── chunker.go # Text splitting │ │ └── chunker.go # Text splitting
│ │ │ │
│ └── persona/ # Persona override │ └── persona/ # Persona override
│ └── loader.go # Carga persona desde YAML │ └── loader.go # Loads persona from YAML
├── data/ ├── data/
│ └── projects/ # ← Markdown por proyecto │ └── projects/ # ← Markdown per project
│ ├── rony-tui.md │ ├── rony-harness.md
│ ├── rony-llm-agent.md │ ├── rony-llm-agent.md
│ └── example-project.md │ └── example-project.md
@ -940,7 +942,7 @@ chat-bot/
│ └── portfolio-bot.yaml # Provider + RAG + persona config │ └── portfolio-bot.yaml # Provider + RAG + persona config
├── docs/ ├── docs/
│ └── architecture.md # ← ESTE ARCHIVO │ └── architecture.md # ← THIS FILE
├── go.mod ├── go.mod
└── README.md └── README.md
@ -950,83 +952,83 @@ chat-bot/
## 📅 11. Roadmap ## 📅 11. Roadmap
### Fase 1: MVP (2-3 semanas) ### Phase 1: MVP (2-3 weeks)
- [ ] Setup proyecto (`go mod init`, estructura) - [ ] Project setup (`go mod init`, structure)
- [ ] HTTP server básico con un endpoint `/api/chat` - [ ] Basic HTTP server with `/api/chat` endpoint
- [ ] SSE streaming funcional - [ ] Functional SSE streaming
- [ ] RAG indexer (lee `data/projects/*.md` → ChromaDB) - [ ] RAG indexer (reads `data/projects/*.md` → ChromaDB)
- [ ] RAG retriever (query → top-k chunks) - [ ] RAG retriever (query → top-k chunks)
- [ ] Persona loader desde YAML - [ ] Persona loader from YAML
- [ ] Integración con Ollama (qwen2.5:1.5b) - [ ] Ollama integration (qwen2.5:1.5b)
- [ ] CLI: `serve`, `reindex`, `ask` - [ ] CLI: `serve`, `reindex`, `ask`
- [ ] Tests básicos - [ ] Basic tests
### Fase 2: Integración con Astro (1 semana) ### Phase 2: Integration with Astro (1 week)
- [ ] Astro API route del proxy - [ ] Astro API route of the proxy
- [ ] React component del chat widget - [ ] React component of the chat widget
- [ ] E2E test: Astro → chat-bot → respuesta - [ ] E2E test: Astro → chat-bot → response
- [ ] Styling del widget (TailwindCSS) - [ ] Widget styling (TailwindCSS)
### Fase 3: Polish (1 semana) ### Phase 3: Polish (1 week)
- [ ] Rate limiting robusto - [ ] Robust rate limiting
- [ ] Logging estructurado (JSON) - [ ] Structured logging (JSON)
- [ ] Health checks para monitoring - [ ] Health checks for monitoring
- [ ] systemd service file - [ ] systemd service file
- [ ] README + docs de deployment - [ ] README + deployment docs
### Fase 4: Opcionales ### Phase 4: Optionals
- [ ] Soporte para múltiples conversaciones (session ID) - [ ] Support for multiple conversations (session ID)
- [ ] Historial de chats persistido - [ ] Persisted chat history
- [ ] Análisis de preguntas frecuentes - [ ] Analysis of frequent questions
- [ ] Multi-idioma (EN/ES switch) - [ ] Multi-language (EN/ES switch)
- [ ] Versión standalone CLI más pulida (`chat-bot ask`) - [ ] More polished standalone CLI version (`chat-bot ask`)
--- ---
## 📐 12. Especificaciones de Calidad ## 📐 12. Quality Specifications
### 12.1 Métricas de rendimiento ### 12.1 Performance metrics
| Métrica | Target | | Metric | Target |
|---|---| |---|---|
| TTFT (Time-to-first-token) | <500ms con Ollama local | | TTFT (Time-to-first-token) | <500ms with Ollama local |
| End-to-end (pregunta → respuesta completa) | <3s para respuestas típicas | | End-to-end (question → complete response) | <3s for typical responses |
| Memoria en reposo | <150MB | | Memory at rest | <150MB |
| RAG indexing speed | ~100 docs/segundo | | RAG indexing speed | ~100 docs/second |
| Retrieval latency | <50ms para top-5 | | Retrieval latency | <50ms for top-5 |
### 12.2 Pruebas requeridas ### 12.2 Required tests
- Unit tests: cobertura ≥70% - Unit tests: coverage ≥70%
- Integration tests: con mock LLM + mock ChromaDB - Integration tests: with mock LLM + mock ChromaDB
- E2E: al menos un flujo completo Astro → chat-bot - E2E: at least one complete Astro → chat-bot flow
--- ---
## 🔒 13. Seguridad ## 🔒 13. Security
### 13.1 Implementado ### 13.1 Implemented
- **Rate limiting** por IP (default 30 req/min) - **Rate limiting** per IP (default 30 req/min)
- **CORS restrictivo** — solo origins configurados - **Restrictive CORS** — only configured origins
- **Input validation** — JSON schema validation en requests - **Input validation** — JSON schema validation on requests
- **No PII storage**no guardamos conversaciones por default - **No PII storage**we don't save conversations by default
- **Local-only por default** — sin llamadas a APIs cloud - **Local-only by default** — no calls to cloud APIs
### 13.2 Diferido / Opcional ### 13.2 Deferred / Optional
- Auth con API key (para uso privado) - Auth with API key (for private use)
- Logging de queries para analytics - Query logging for analytics
- Anonymization de IPs en logs - IP anonymization in logs
- HTTPS via reverse proxy (Caddy/nginx) - HTTPS via reverse proxy (Caddy/nginx)
--- ---
## 📚 14. Referencias ## 📚 14. References
- **SSE Spec:** https://html.spec.whatwg.org/multipage/server-sent-events.html - **SSE Spec:** https://html.spec.whatwg.org/multipage/server-sent-events.html
- **Ollama API:** https://github.com/ollama/ollama/blob/main/docs/api.md - **Ollama API:** https://github.com/ollama/ollama/blob/main/docs/api.md
@ -1038,4 +1040,4 @@ chat-bot/
--- ---
**Documento listo para implementación. 🚀** **Document ready for implementation. 🚀**