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:
parent
fcf0e6251a
commit
b9769fac41
10 changed files with 1658 additions and 316 deletions
109
README.es.md
Normal file
109
README.es.md
Normal 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)
|
||||||
94
README.md
94
README.md
|
|
@ -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)
|
||||||
82
configs/portfolio-bot.es.yaml
Normal file
82
configs/portfolio-bot.es.yaml
Normal 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
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
52
data/projects/README.es.md
Normal file
52
data/projects/README.es.md
Normal 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.
|
||||||
|
|
@ -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.
|
||||||
45
data/projects/example-project.es.md
Normal file
45
data/projects/example-project.es.md
Normal 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
|
||||||
|
|
@ -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
1044
docs/architecture.es.md
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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. 🚀**
|
||||||
Loading…
Reference in a new issue