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
|
||||
|
||||
- 🌐 **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.)
|
||||
- 🌐 **HTTP server** with SSE (Server-Sent Events) streaming
|
||||
- 🧠 **RAG over markdown** — automatically indexes `.md` in `data/projects/`
|
||||
- 🎭 **Customizable persona** — responds as "Victor's assistant"
|
||||
- ⚡ **Self-hosted** with Ollama or llama.cpp (no cloud API key required)
|
||||
- 🔌 **Integrable** with Astro/React via HTTP proxy
|
||||
- 🛡️ **Rate limiting** and structured logging
|
||||
- 📦 **Portable** — adaptable to other contexts (clients, products, etc.)
|
||||
|
||||
## 🚀 Quick start
|
||||
|
||||
```bash
|
||||
# 1. Instalar
|
||||
# 1. Install
|
||||
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
|
||||
|
||||
# 3. Configurar provider (ejemplo: Ollama)
|
||||
# Asegúrate de tener Ollama corriendo: ollama serve
|
||||
# Modelo descargado: ollama pull qwen2.5:1.5b
|
||||
# 3. Configure provider (e.g., Ollama)
|
||||
# Make sure Ollama is running: ollama serve
|
||||
# Downloaded model: ollama pull qwen2.5:1.5b
|
||||
|
||||
# 4. Cargar tus proyectos en data/projects/
|
||||
echo "# Mi Proyecto Cool\nDescripción..." > data/projects/mi-proyecto.md
|
||||
# 4. Load your projects in data/projects/
|
||||
echo "# My Cool Project\nDescription..." > data/projects/my-project.md
|
||||
|
||||
# 5. Build
|
||||
go build -o bin/chat-bot ./cmd/chat-bot
|
||||
|
||||
# 6. Run
|
||||
./bin/chat-bot serve
|
||||
# → Sirve en http://localhost:7331
|
||||
# → Serves on http://localhost:7331
|
||||
```
|
||||
|
||||
## 📁 Estructura
|
||||
## 📁 Structure
|
||||
|
||||
```
|
||||
chat-bot/
|
||||
├── cm./rony-chat-bot/ # Entry point (CLI)
|
||||
├── cmd/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
|
||||
├── data/projects/ # ← YOUR PROJECTS IN MARKDOWN
|
||||
│ ├── rony-harness.md
|
||||
│ ├── rony-llm-agent.md
|
||||
│ └── ...
|
||||
├── configs/
|
||||
│ └── portfolio-bot.yaml # Provider config
|
||||
├── docs/
|
||||
│ └── architecture.md # ← Especificación técnica completa
|
||||
│ └── architecture.md # ← Complete technical specification
|
||||
└── 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
|
||||
// portfolio/src/pages/api/chat.ts
|
||||
|
|
@ -70,37 +72,43 @@ export const POST: APIRoute = async ({ request }) => {
|
|||
const body = await request.json();
|
||||
const resp = await fetch('http://localhost:7331/api/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(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
|
||||
2. Reemplaza `data/projects/` con `data/inventory/` (u otro dominio)
|
||||
3. Actualiza `configs/portfolio-bot.yaml` con la nueva persona
|
||||
1. Fork/clone this repo
|
||||
2. Replace `data/projects/` with `data/inventory/` (or another domain)
|
||||
3. Update `configs/portfolio-bot.yaml` with the new persona
|
||||
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
|
||||
- [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
|
||||
- [**Architecture doc**](./docs/architecture.md) — Complete technical specification
|
||||
- [Library: `rony-llm-agent`](https://github.com/VictorVargas/rony-llm-agent) — Reusable core
|
||||
- [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
|
||||
- [`harness`](https://github.com/VictorVargas/rony-harness) — AI agent harness (TUI)
|
||||
- [`portfolio`](https://github.com/VictorVargas/portfolio) — Astro + React site (integra este bot)
|
||||
- [`rony-llm-agent`](https://github.com/VictorVargas/rony-llm-agent) — Core library
|
||||
- [`rony-harness`](https://github.com/VictorVargas/rony-harness) — AI agent harness (TUI)
|
||||
- [`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
|
||||
# Documentación: https://github.com/VictorVargas/rony-llm-agent/pkg/llm
|
||||
# Portfolio Bot Configuration
|
||||
# Documentation: https://github.com/VictorVargas/rony-llm-agent/pkg/llm
|
||||
|
||||
server:
|
||||
host: "0.0.0.0"
|
||||
|
|
@ -7,73 +7,73 @@ server:
|
|||
read_timeout_ms: 30000
|
||||
cors_origins:
|
||||
- "http://localhost:4321" # Astro dev server
|
||||
- "https://victorvargas.dev" # Producción (cuando exista)
|
||||
- "https://victorvargas.dev" # Production (when it exists)
|
||||
rate_limit:
|
||||
requests_per_minute: 30 # Por IP
|
||||
requests_per_minute: 30 # Per IP
|
||||
burst: 5
|
||||
|
||||
# Providers LLM (al menos uno configurado)
|
||||
# LLM providers (at least one configured)
|
||||
providers:
|
||||
# === Ollama (recomendado para desarrollo) ===
|
||||
# === Ollama (recommended for development) ===
|
||||
- name: ollama-local
|
||||
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
|
||||
default: true
|
||||
|
||||
# === llama.cpp directo (GGUF) ===
|
||||
# === llama.cpp direct (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) ===
|
||||
# === Anthropic (if you want quality > privacy) ===
|
||||
- name: anthropic-api
|
||||
type: anthropic
|
||||
model: claude-haiku-4 # Modelo barato
|
||||
model: claude-haiku-4 # Cheap model
|
||||
api_key_env: ANTHROPIC_API_KEY
|
||||
|
||||
# RAG: cómo se indexan los proyectos
|
||||
# RAG: how projects are indexed
|
||||
rag:
|
||||
enabled: true
|
||||
data_path: ./data/projects # Directorio con .md
|
||||
chunk_size: 500 # caracteres por chunk
|
||||
data_path: ./data/projects # Directory with .md
|
||||
chunk_size: 500 # characters per chunk
|
||||
chunk_overlap: 50
|
||||
embedding_provider: ollama # o llamacpp
|
||||
embedding_provider: ollama # or llamacpp
|
||||
embedding_model: nomic-embed-text
|
||||
vector_db_path: ./chroma # Persistencia local
|
||||
top_k: 5 # Documentos a recuperar por query
|
||||
vector_db_path: ./chroma # Local persistence
|
||||
top_k: 5 # Documents to retrieve per query
|
||||
rerank: false # Phase 2
|
||||
|
||||
# Persona: quién es el bot
|
||||
# Persona: who the bot is
|
||||
persona:
|
||||
name: "Rony Chat Bot"
|
||||
tone: "Profesional, conocedor, amable"
|
||||
language: "Español"
|
||||
tone: "Professional, knowledgeable, friendly"
|
||||
language: "English"
|
||||
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."
|
||||
- "Only answer about Victor and his projects"
|
||||
- "If you don't know, say 'I don't have that information'"
|
||||
- "Be concise but informative"
|
||||
- "Use markdown format for lists and code"
|
||||
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: |
|
||||
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:
|
||||
- Los proyectos de Victor (ver archivos en data/projects/)
|
||||
- Su experiencia y skills técnicas
|
||||
- Su enfoque de trabajo
|
||||
Your job is to answer questions about:
|
||||
- Victor's projects (see files in data/projects/)
|
||||
- His experience and technical skills
|
||||
- His work approach
|
||||
|
||||
Responde en español, con tono profesional pero accesible.
|
||||
Si te preguntan algo que no está en tu contexto, dilo honestamente.
|
||||
Respond in English, with professional but accessible tone.
|
||||
If you're asked something not in your context, say it honestly.
|
||||
|
||||
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
|
||||
Recommended format:
|
||||
- Use markdown for lists, code, and emphasis
|
||||
- Be concise (max 2-3 paragraphs per response)
|
||||
- Include links to repos when relevant
|
||||
|
||||
# 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`
|
||||
- Nombre en kebab-case (minúsculas con guiones)
|
||||
- Ejemplo: `rony-tui.md`, `rony-llm-agent.md`, `portfolio-astro.md`
|
||||
- One file per project: `project-name.md`
|
||||
- Name in kebab-case (lowercase with hyphens)
|
||||
- Example: `rony-harness.md`, `rony-llm-agent.md`, `portfolio-astro.md`
|
||||
|
||||
## Frontmatter (opcional pero recomendado)
|
||||
## Frontmatter (optional but recommended)
|
||||
|
||||
```markdown
|
||||
---
|
||||
title: "Rony TUI"
|
||||
title: "Rony Harness"
|
||||
date: 2026-06
|
||||
status: "active" # active | archived | wip
|
||||
tags: ["go", "ai", "cli"]
|
||||
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
|
||||
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
|
||||
1. The bot scans this directory on startup
|
||||
2. Each `.md` is split into chunks of ~500 characters
|
||||
3. Each chunk is converted to embedding with Ollama
|
||||
4. Embeddings are stored in ChromaDB
|
||||
5. When someone asks a question, the top-5 most relevant chunks are searched
|
||||
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
|
||||
./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
|
||||
status: "active"
|
||||
tags: ["ejemplo", "plantilla"]
|
||||
tags: ["example", "template"]
|
||||
repo: ""
|
||||
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
|
||||
- **Framework:** Ninguno (stdlib)
|
||||
- **Base de datos:** SQLite
|
||||
- **Language:** Go 1.26
|
||||
- **Framework:** None (stdlib)
|
||||
- **Database:** SQLite
|
||||
- **Deployment:** Fly.io
|
||||
|
||||
## Features principales
|
||||
## Main features
|
||||
|
||||
1. Feature uno — descripción breve
|
||||
2. Feature dos — descripción breve
|
||||
3. Feature tres — descripción breve
|
||||
1. Feature one — brief description
|
||||
2. Feature two — brief description
|
||||
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
|
||||
|
||||
- Repo: github.com/VictorVargas/proyecto
|
||||
- Demo: proyecto.example.com
|
||||
- Docs: docs.proyecto.example.com
|
||||
- Repo: github.com/VictorVargas/project
|
||||
- Demo: project.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
|
||||
|
||||
**Versión:** 1.0
|
||||
**Autor:** Victor Hugo Vargas
|
||||
**Fecha:** 2026-06-28
|
||||
**Estado:** Especificación completa para implementación
|
||||
**Version:** 1.0
|
||||
**Author:** Victor Hugo Vargas
|
||||
**Date:** 2026-06-28
|
||||
**Status:** Complete specification for implementation
|
||||
**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:
|
||||
- "¿Qué proyectos ha hecho Victor?"
|
||||
- "¿Cuál es su experiencia con Go?"
|
||||
- "¿Cómo funciona Rony TUI?"
|
||||
- "¿Victor ha trabajado con PostgreSQL?"
|
||||
Victor has a portfolio website (Astro + React). On the site there's a chat widget where visitors can ask:
|
||||
|
||||
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.
|
||||
- **Standalone CLI:** `./chat-bot ask "¿qué sabes de X?"` para uso desde terminal.
|
||||
- **Slack/Discord bot:** Wrapper que consume el HTTP API.
|
||||
### 1.3 Secondary use cases (future)
|
||||
|
||||
### 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
|
||||
- **Cloud opcional** — si se necesita más calidad, swap a Anthropic API
|
||||
- **Portable** — fácil de fork/customizar para otros contextos
|
||||
- **Streaming** — respuestas token-por-token con SSE (no espera a respuesta completa)
|
||||
- **Reutiliza `rony-llm-agent`** — no reinventar el agent loop
|
||||
### 1.4 Philosophy
|
||||
|
||||
- **Self-hosted by default** — works 100% local with Ollama + 1-3B models
|
||||
- **Cloud optional** — if you need more quality, swap to Anthropic API
|
||||
- **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) │
|
||||
│ ↓ HTTP POST /api/chat │
|
||||
│ Astro SSR (proxy) ←────────── Sirve portfolio + proxy chat │
|
||||
│ Astro SSR (proxy) ←────────── Serves portfolio + proxy chat │
|
||||
│ ↓ HTTP POST /api/chat │
|
||||
│ Chat-Bot HTTP server (:7331) │
|
||||
│ ↓ │
|
||||
│ Agent loop (rony-llm-agent) │
|
||||
│ ↓ │
|
||||
│ RAG retrieval → ChromaDB sobre data/projects/*.md │
|
||||
│ RAG retrieval → ChromaDB over data/projects/*.md │
|
||||
│ ↓ │
|
||||
│ 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 |
|
||||
| **Agent runner** | `internal/agent/` | Wrapper sobre `rony-llm-agent` con config específica |
|
||||
| **Portfolio loader** | `internal/portfolio/` | Lee `data/projects/*.md`, indexa en ChromaDB |
|
||||
| **Persona** | `internal/persona/` | Carga persona desde `configs/portfolio-bot.yaml` |
|
||||
| **CLI** | `cm./rony-chat-bot/` | Comandos: `serve`, `reindex`, `ask`, `version` |
|
||||
| **Agent runner** | `internal/agent/` | Wrapper over `rony-llm-agent` with specific config |
|
||||
| **Portfolio loader** | `internal/portfolio/` | Reads `data/projects/*.md`, indexes in ChromaDB |
|
||||
| **Persona** | `internal/persona/` | Loads persona from `configs/portfolio-bot.yaml` |
|
||||
| **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` |
|
||||
| **HTTP router** | `net/http` + `chi` | Stdlib + chi para middleware (CORS, logging) |
|
||||
| **SSE** | `net/http` Flusher | Stdlib es suficiente, no necesita librería externa |
|
||||
| **Config** | `gopkg.in/yaml.v3` | Mismo que harness |
|
||||
| **Language** | Go 1.26+ | Same as rony-harness, leverage `os.Root`, `iter.Seq` |
|
||||
| **HTTP router** | `net/http` + `chi` | Stdlib + chi for middleware (CORS, logging) |
|
||||
| **SSE** | `net/http` Flusher | Stdlib is enough, no external library needed |
|
||||
| **Config** | `gopkg.in/yaml.v3` | Same as harness |
|
||||
| **RAG backend** | ChromaDB embedded via `chroma-go` | Self-hosted, simple API |
|
||||
| **Embeddings** | Ollama (nomic-embed-text) | Local, gratis, buena calidad |
|
||||
| **LLM** | Ollama (qwen2.5:1.5b) o llama.cpp | Self-hosted por defecto |
|
||||
| **Tests** | stdlib + testify | Consistencia con el resto |
|
||||
| **Embeddings** | Ollama (nomic-embed-text) | Local, free, good quality |
|
||||
| **LLM** | Ollama (qwen2.5:1.5b) or llama.cpp | Self-hosted by default |
|
||||
| **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
|
||||
|
||||
#### `POST /api/chat` — Chat con streaming SSE
|
||||
#### `POST /api/chat` — Chat with SSE streaming
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"messages": [
|
||||
{"role": "user", "content": "¿Qué proyectos tiene Victor?"}
|
||||
{"role": "user", "content": "What projects does Victor have?"}
|
||||
],
|
||||
"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":"chunk","content":"Victor"}
|
||||
data: {"type":"chunk","content":" tiene"}
|
||||
data: {"type":"chunk","content":" varios"}
|
||||
data: {"type":"chunk","content":" proyectos"}
|
||||
data: {"type":"chunk","content":" has"}
|
||||
data: {"type":"chunk","content":" several"}
|
||||
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}}
|
||||
```
|
||||
|
||||
**Sin streaming** (`"stream": false`):
|
||||
**Without streaming** (`"stream": false`):
|
||||
```json
|
||||
{
|
||||
"content": "Victor tiene varios proyectos...",
|
||||
"sources": ["rony-tui.md", "rony-llm-agent.md"],
|
||||
"content": "Victor has several projects...",
|
||||
"sources": ["rony-harness.md", "rony-llm-agent.md"],
|
||||
"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:**
|
||||
```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
|
||||
{
|
||||
"name": "Asistente de Victor Hugo Vargas",
|
||||
"name": "Rony Chat Bot",
|
||||
"model": "qwen2.5:1.5b",
|
||||
"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) {
|
||||
// Headers SSE
|
||||
// SSE headers
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
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)
|
||||
if !ok {
|
||||
http.Error(w, "SSE no soportado", http.StatusInternalServerError)
|
||||
http.Error(w, "SSE not supported", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -208,7 +211,7 @@ func (s *Server) handleChat(w http.ResponseWriter, r *http.Request) {
|
|||
"conversation_id": generateConvID(),
|
||||
})
|
||||
|
||||
// Run agent con streaming
|
||||
// Run agent with streaming
|
||||
sources := []string{}
|
||||
for chunk, err := range s.agent.RunStream(r.Context(), req.Messages) {
|
||||
if err != nil {
|
||||
|
|
@ -246,7 +249,7 @@ package server
|
|||
func (s *Server) loggingMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
// Wrap response writer para capturar status
|
||||
// Wrap response writer to capture status
|
||||
rw := &statusRecorder{ResponseWriter: w, status: 200}
|
||||
next.ServeHTTP(rw, r)
|
||||
|
||||
|
|
@ -295,7 +298,7 @@ func (s *Server) rateLimitMiddleware(next http.Handler) http.Handler {
|
|||
|
||||
## 🧠 4. RAG (Retrieval-Augmented Generation)
|
||||
|
||||
### 4.1 Pipeline de indexación
|
||||
### 4.1 Indexing pipeline
|
||||
|
||||
```
|
||||
data/projects/*.md
|
||||
|
|
@ -309,26 +312,26 @@ Vectors [][]float32
|
|||
Indexed corpus
|
||||
```
|
||||
|
||||
**Cuándo se ejecuta:**
|
||||
- Al arrancar el bot (si `--reindex-on-start` flag)
|
||||
- Manualmente: `./chat-bot reindex`
|
||||
- Vía HTTP: `POST /api/reindex`
|
||||
**When it runs:**
|
||||
- On bot startup (if `--reindex-on-start` flag)
|
||||
- Manually: `./chat-bot 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)
|
||||
Query vector
|
||||
↓ (cosine similarity search en ChromaDB, top_k=5)
|
||||
Top 5 chunks relevantes
|
||||
↓ (cosine similarity search in ChromaDB, top_k=5)
|
||||
Top 5 relevant chunks
|
||||
↓ (format as context block)
|
||||
System prompt += chunks relevantes
|
||||
System prompt += relevant chunks
|
||||
↓ (send to LLM)
|
||||
LLM generates answer
|
||||
```
|
||||
|
||||
### 4.3 Implementación
|
||||
### 4.3 Implementation
|
||||
|
||||
```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 {
|
||||
// Implementación simple: split por tamaño con overlap
|
||||
// Versión production usa tokenizer-aware chunking
|
||||
// Simple implementation: split by size with overlap
|
||||
// Production version uses tokenizer-aware chunking
|
||||
var chunks []string
|
||||
for i := 0; i < len(text); i += size - overlap {
|
||||
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
|
||||
// internal/agent/runner.go
|
||||
|
|
@ -437,9 +440,9 @@ func (r *Runner) buildSystemPrompt(ctx context.Context, query string) (string, e
|
|||
// 3. Format as context
|
||||
var contextBlock strings.Builder
|
||||
contextBlock.WriteString(basePrompt)
|
||||
contextBlock.WriteString("\n\n## Contexto relevante\n\n")
|
||||
contextBlock.WriteString("\n\n## Relevant context\n\n")
|
||||
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))
|
||||
}
|
||||
|
||||
|
|
@ -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]
|
||||
```
|
||||
|
||||
**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)
|
||||
- ✅ Astro maneja auth/sesión si se necesita
|
||||
- ✅ Puede haber rate limiting centralizado en Astro
|
||||
- ✅ El chat-bot queda en red privada (no expuesto a internet directamente)
|
||||
- ✅ Astro handles auth/session if needed
|
||||
- ✅ There can be centralized rate limiting in Astro
|
||||
- ✅ 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
|
||||
// portfolio/src/pages/api/chat.ts
|
||||
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 }) => {
|
||||
const body = await request.json();
|
||||
|
||||
const resp = await fetch(`${CHAT_BOT_URL}/api/chat`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
|
@ -506,7 +508,7 @@ export const POST: APIRoute = async ({ request }) => {
|
|||
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, {
|
||||
status: 200,
|
||||
headers: {
|
||||
|
|
@ -518,7 +520,7 @@ export const POST: APIRoute = async ({ request }) => {
|
|||
};
|
||||
```
|
||||
|
||||
### 5.3 React: Componente del chat
|
||||
### 5.3 React: Chat component
|
||||
|
||||
```tsx
|
||||
// portfolio/src/components/Chat.tsx
|
||||
|
|
@ -543,7 +545,7 @@ export default function Chat() {
|
|||
setInput('');
|
||||
setStreaming(true);
|
||||
|
||||
// Placeholder para streaming
|
||||
// Placeholder for streaming
|
||||
const assistantMsg: Message = { role: 'assistant', content: '' };
|
||||
setMessages(prev => [...prev, assistantMsg]);
|
||||
|
||||
|
|
@ -611,7 +613,7 @@ export default function Chat() {
|
|||
value={input}
|
||||
onChange={e => setInput(e.target.value)}
|
||||
onKeyDown={e => e.key === 'Enter' && send()}
|
||||
placeholder="Pregunta sobre Victor..."
|
||||
placeholder="Ask about Victor..."
|
||||
disabled={streaming}
|
||||
/>
|
||||
{streaming ? (
|
||||
|
|
@ -627,39 +629,39 @@ export default function Chat() {
|
|||
|
||||
---
|
||||
|
||||
## 🤖 6. Self-hosting con Ollama
|
||||
## 🤖 6. Self-hosting with Ollama
|
||||
|
||||
### 6.1 Setup
|
||||
|
||||
```bash
|
||||
# 1. Instalar Ollama
|
||||
# 1. Install Ollama
|
||||
curl -fsSL https://ollama.com/install.sh | sh
|
||||
|
||||
# 2. Descargar modelo de chat
|
||||
# 2. Download chat model
|
||||
ollama pull qwen2.5:1.5b
|
||||
|
||||
# 3. Descargar modelo de embeddings
|
||||
# 3. Download embeddings model
|
||||
ollama pull nomic-embed-text
|
||||
|
||||
# 4. Verificar
|
||||
# 4. Verify
|
||||
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
|
||||
# Asegurar que Ollama está corriendo
|
||||
# Make sure Ollama is running
|
||||
ollama serve
|
||||
|
||||
# Arrancar el bot
|
||||
# Start the bot
|
||||
./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
|
||||
providers:
|
||||
|
|
@ -667,42 +669,42 @@ providers:
|
|||
type: llamacpp
|
||||
model_path: ${RONY_MODELS_PATH}/qwen2.5-1.5b-instruct-q5_k_m.gguf
|
||||
context_size: 4096
|
||||
n_gpu_layers: 999 # offload todo a GPU
|
||||
n_gpu_layers: 999 # offload all to GPU
|
||||
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
|
||||
# Arrancar servidor HTTP
|
||||
# Start HTTP server
|
||||
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
|
||||
|
||||
# Pregunta única (sin servidor, útil para tests)
|
||||
chat-bot ask "¿Qué proyectos tiene Victor?" [--no-rag]
|
||||
# Single question (no server, useful for tests)
|
||||
chat-bot ask "What projects does Victor have?" [--no-rag]
|
||||
|
||||
# Validar config
|
||||
# Validate config
|
||||
chat-bot config validate
|
||||
|
||||
# Health check (útil para monitoring)
|
||||
# Health check (useful for monitoring)
|
||||
chat-bot health
|
||||
|
||||
# Versión
|
||||
# Version
|
||||
chat-bot version
|
||||
```
|
||||
|
||||
### 7.2 Implementación con Cobra
|
||||
### 7.2 Implementation with Cobra
|
||||
|
||||
```go
|
||||
// cm./rony-chat-bot/main.go
|
||||
// cmd/chat-bot/main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
@ -756,10 +758,10 @@ func serveCmd() *cobra.Command {
|
|||
|
||||
## 🚀 8. Deployment
|
||||
|
||||
### 8.1 Recomendación: Self-hosted en VPS
|
||||
### 8.1 Recommendation: Self-hosted on VPS
|
||||
|
||||
```bash
|
||||
# 1. Instalar dependencias
|
||||
# 1. Install dependencies
|
||||
sudo apt install golang-go ollama
|
||||
ollama pull qwen2.5:1.5b
|
||||
ollama pull nomic-embed-text
|
||||
|
|
@ -800,7 +802,7 @@ chat.victorvargas.dev {
|
|||
### 8.3 Monitoring
|
||||
|
||||
```bash
|
||||
# Health check periódico
|
||||
# Health check periodic
|
||||
curl -s http://localhost:7331/api/health | jq
|
||||
|
||||
# Logs
|
||||
|
|
@ -821,7 +823,7 @@ func TestHandleChat_ValidRequest(t *testing.T) {
|
|||
s := newTestServer(t)
|
||||
|
||||
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")
|
||||
|
||||
|
|
@ -838,31 +840,31 @@ func TestHandleChat_RateLimit(t *testing.T) {
|
|||
})
|
||||
|
||||
// First request OK
|
||||
req1 := newChatRequest("hola")
|
||||
req1 := newChatRequest("hello")
|
||||
w1 := httptest.NewRecorder()
|
||||
s.handleChat(w1, req1)
|
||||
assert.Equal(t, 200, w1.Code)
|
||||
|
||||
// Second request denied
|
||||
req2 := newChatRequest("hola de nuevo")
|
||||
req2 := newChatRequest("hello again")
|
||||
w2 := httptest.NewRecorder()
|
||||
s.handleChat(w2, req2)
|
||||
assert.Equal(t, 429, w2.Code)
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 Integration tests con mock LLM
|
||||
### 9.2 Integration tests with mock LLM
|
||||
|
||||
```go
|
||||
// internal/agent/runner_test.go
|
||||
func TestRunner_RAGContextIsInjected(t *testing.T) {
|
||||
mockLLM := mock.New(mock.Responses{
|
||||
{Match: "proyectos", Response: "Victor tiene varios proyectos..."},
|
||||
{Match: "projects", Response: "Victor has several projects..."},
|
||||
})
|
||||
|
||||
memory := newMockMemoryWithDocs(t, []rag.Fragment{
|
||||
{Content: "Rony TUI: AI agent harness...", ProjectID: "rony-tui"},
|
||||
{Content: "rony-llm-agent: librería Go...", ProjectID: "rony-llm-agent"},
|
||||
{Content: "Rony Harness: AI agent harness...", ProjectID: "rony-harness"},
|
||||
{Content: "go-llm-agent: Go library...", ProjectID: "rony-llm-agent"},
|
||||
})
|
||||
|
||||
runner := agent.NewRunner(agent.Config{
|
||||
|
|
@ -872,36 +874,36 @@ func TestRunner_RAGContextIsInjected(t *testing.T) {
|
|||
})
|
||||
|
||||
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
|
||||
lastReq := mockLLM.LastRequest()
|
||||
assert.Contains(t, lastReq.Messages[0].Content, "Rony TUI")
|
||||
assert.Contains(t, lastReq.Messages[0].Content, "rony-llm-agent")
|
||||
assert.Contains(t, lastReq.Messages[0].Content, "Rony Harness")
|
||||
assert.Contains(t, lastReq.Messages[0].Content, "go-llm-agent")
|
||||
}
|
||||
```
|
||||
|
||||
### 9.3 E2E test con Astro
|
||||
### 9.3 E2E test with Astro
|
||||
|
||||
```bash
|
||||
# 1. Arrancar chat-bot en :7331
|
||||
# 1. Start chat-bot on :7331
|
||||
./bin/chat-bot serve &
|
||||
|
||||
# 2. Arrancar Astro en :4321
|
||||
# 2. Start Astro on :4321
|
||||
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 \
|
||||
-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/
|
||||
|
|
@ -918,21 +920,21 @@ chat-bot/
|
|||
│ │ ├── middleware.go # logging, CORS, rate limit
|
||||
│ │ └── sse.go # SSE helpers
|
||||
│ │
|
||||
│ ├── agent/ # Wrapper sobre rony-llm-agent
|
||||
│ │ ├── runner.go # RunStream con RAG injection
|
||||
│ ├── agent/ # Wrapper over rony-llm-agent
|
||||
│ │ ├── runner.go # RunStream with RAG injection
|
||||
│ │ └── prompts.go # System prompt builder
|
||||
│ │
|
||||
│ ├── portfolio/ # Data loader
|
||||
│ │ ├── indexer.go # Lee .md, chunks, embed, store
|
||||
│ │ ├── indexer.go # Reads .md, chunks, embed, store
|
||||
│ │ ├── retriever.go # Query → top-k chunks
|
||||
│ │ └── chunker.go # Text splitting
|
||||
│ │
|
||||
│ └── persona/ # Persona override
|
||||
│ └── loader.go # Carga persona desde YAML
|
||||
│ └── loader.go # Loads persona from YAML
|
||||
│
|
||||
├── data/
|
||||
│ └── projects/ # ← Markdown por proyecto
|
||||
│ ├── rony-tui.md
|
||||
│ └── projects/ # ← Markdown per project
|
||||
│ ├── rony-harness.md
|
||||
│ ├── rony-llm-agent.md
|
||||
│ └── example-project.md
|
||||
│
|
||||
|
|
@ -940,7 +942,7 @@ chat-bot/
|
|||
│ └── portfolio-bot.yaml # Provider + RAG + persona config
|
||||
│
|
||||
├── docs/
|
||||
│ └── architecture.md # ← ESTE ARCHIVO
|
||||
│ └── architecture.md # ← THIS FILE
|
||||
│
|
||||
├── go.mod
|
||||
└── README.md
|
||||
|
|
@ -950,83 +952,83 @@ chat-bot/
|
|||
|
||||
## 📅 11. Roadmap
|
||||
|
||||
### Fase 1: MVP (2-3 semanas)
|
||||
### Phase 1: MVP (2-3 weeks)
|
||||
|
||||
- [ ] Setup proyecto (`go mod init`, estructura)
|
||||
- [ ] HTTP server básico con un endpoint `/api/chat`
|
||||
- [ ] SSE streaming funcional
|
||||
- [ ] RAG indexer (lee `data/projects/*.md` → ChromaDB)
|
||||
- [ ] Project setup (`go mod init`, structure)
|
||||
- [ ] Basic HTTP server with `/api/chat` endpoint
|
||||
- [ ] Functional SSE streaming
|
||||
- [ ] RAG indexer (reads `data/projects/*.md` → ChromaDB)
|
||||
- [ ] RAG retriever (query → top-k chunks)
|
||||
- [ ] Persona loader desde YAML
|
||||
- [ ] Integración con Ollama (qwen2.5:1.5b)
|
||||
- [ ] Persona loader from YAML
|
||||
- [ ] Ollama integration (qwen2.5:1.5b)
|
||||
- [ ] 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
|
||||
- [ ] React component del chat widget
|
||||
- [ ] E2E test: Astro → chat-bot → respuesta
|
||||
- [ ] Styling del widget (TailwindCSS)
|
||||
- [ ] Astro API route of the proxy
|
||||
- [ ] React component of the chat widget
|
||||
- [ ] E2E test: Astro → chat-bot → response
|
||||
- [ ] Widget styling (TailwindCSS)
|
||||
|
||||
### Fase 3: Polish (1 semana)
|
||||
### Phase 3: Polish (1 week)
|
||||
|
||||
- [ ] Rate limiting robusto
|
||||
- [ ] Logging estructurado (JSON)
|
||||
- [ ] Health checks para monitoring
|
||||
- [ ] Robust rate limiting
|
||||
- [ ] Structured logging (JSON)
|
||||
- [ ] Health checks for monitoring
|
||||
- [ ] systemd service file
|
||||
- [ ] README + docs de deployment
|
||||
- [ ] README + deployment docs
|
||||
|
||||
### Fase 4: Opcionales
|
||||
### Phase 4: Optionals
|
||||
|
||||
- [ ] Soporte para múltiples conversaciones (session ID)
|
||||
- [ ] Historial de chats persistido
|
||||
- [ ] Análisis de preguntas frecuentes
|
||||
- [ ] Multi-idioma (EN/ES switch)
|
||||
- [ ] Versión standalone CLI más pulida (`chat-bot ask`)
|
||||
- [ ] Support for multiple conversations (session ID)
|
||||
- [ ] Persisted chat history
|
||||
- [ ] Analysis of frequent questions
|
||||
- [ ] Multi-language (EN/ES switch)
|
||||
- [ ] 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 |
|
||||
| End-to-end (pregunta → respuesta completa) | <3s para respuestas típicas |
|
||||
| Memoria en reposo | <150MB |
|
||||
| RAG indexing speed | ~100 docs/segundo |
|
||||
| Retrieval latency | <50ms para top-5 |
|
||||
| TTFT (Time-to-first-token) | <500ms with Ollama local |
|
||||
| End-to-end (question → complete response) | <3s for typical responses |
|
||||
| Memory at rest | <150MB |
|
||||
| RAG indexing speed | ~100 docs/second |
|
||||
| Retrieval latency | <50ms for top-5 |
|
||||
|
||||
### 12.2 Pruebas requeridas
|
||||
### 12.2 Required tests
|
||||
|
||||
- Unit tests: cobertura ≥70%
|
||||
- Integration tests: con mock LLM + mock ChromaDB
|
||||
- E2E: al menos un flujo completo Astro → chat-bot
|
||||
- Unit tests: coverage ≥70%
|
||||
- Integration tests: with mock LLM + mock ChromaDB
|
||||
- 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)
|
||||
- **CORS restrictivo** — solo origins configurados
|
||||
- **Input validation** — JSON schema validation en requests
|
||||
- **No PII storage** — no guardamos conversaciones por default
|
||||
- **Local-only por default** — sin llamadas a APIs cloud
|
||||
- **Rate limiting** per IP (default 30 req/min)
|
||||
- **Restrictive CORS** — only configured origins
|
||||
- **Input validation** — JSON schema validation on requests
|
||||
- **No PII storage** — we don't save conversations by default
|
||||
- **Local-only by default** — no calls to cloud APIs
|
||||
|
||||
### 13.2 Diferido / Opcional
|
||||
### 13.2 Deferred / Optional
|
||||
|
||||
- Auth con API key (para uso privado)
|
||||
- Logging de queries para analytics
|
||||
- Anonymization de IPs en logs
|
||||
- Auth with API key (for private use)
|
||||
- Query logging for analytics
|
||||
- IP anonymization in logs
|
||||
- HTTPS via reverse proxy (Caddy/nginx)
|
||||
|
||||
---
|
||||
|
||||
## 📚 14. Referencias
|
||||
## 📚 14. References
|
||||
|
||||
- **SSE Spec:** https://html.spec.whatwg.org/multipage/server-sent-events.html
|
||||
- **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