Patrón Blackboard
Un espacio de conocimiento compartido donde módulos especializados independientes colaboran para resolver problemas complejos contribuyendo soluciones parciales.
Descripción General
El Patrón Blackboard te da un espacio de conocimiento compartido — el blackboard — donde módulos independientes y especializados llamados Knowledge Sources trabajan juntos en un problema. Una fuente lee el tablero, se pregunta “¿puedo aportar algo útil aquí?”, y publica una solución parcial si la respuesta es sí.
El patrón se gana su lugar cuando ningún algoritmo por sí solo puede resolver el problema, pero un puñado de heurísticas especializadas pueden ir desgastándolo. Un componente Control ejecuta el bucle, decidiendo qué fuente actúa a continuación a partir del estado actual del blackboard.
Lo he visto aplicado en reconocimiento de voz, PLN, reconocimiento de imágenes y optimización por restricciones — dominios donde la respuesta emerge de muchas pequeñas contribuciones en lugar de un algoritmo decisivo.
Cuándo Usar
Usa el Patrón Blackboard cuando:
- Ningún algoritmo determinístico puede resolver el problema
- Varias heurísticas o algoritmos especializados pueden aportar soluciones parciales
- El espacio del problema es suficientemente grande como para necesitar exploración
- Módulos independientes pueden refinar incrementalmente una solución
- Quieres una arquitectura extensible para un sistema de IA o heurísticas
Cuándo Evitar
- Problemas que un solo algoritmo resuelve limpiamente
- Presupuestos de latencia estrictos donde la sobrecarga de coordinación es inaceptable
- Situaciones que necesitan comportamiento determinístico y predecible
- Sistemas pequeños donde la coordinación cuesta más de lo que ahorra
Solución
Python
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import List, Dict, Any, Optional
from enum import Enum
import random
class Confidence(Enum):
LOW = 1
MEDIUM = 2
HIGH = 3
@dataclass
class Hypothesis:
content: str
confidence: Confidence
source: str
@dataclass
class Blackboard:
hypotheses: List[Hypothesis] = field(default_factory=list)
final_solution: Optional[str] = None
complete: bool = False
def add_hypothesis(self, hypothesis: Hypothesis):
self.hypotheses.append(hypothesis)
def get_best_hypothesis(self) -> Optional[Hypothesis]:
if not self.hypotheses:
return None
return max(self.hypotheses, key=lambda h: h.confidence.value)
def set_solution(self, solution: str):
self.final_solution = solution
self.complete = True
class KnowledgeSource(ABC):
def __init__(self, name: str):
self.name = name
@abstractmethod
def can_contribute(self, blackboard: Blackboard) -> bool:
pass
@abstractmethod
def contribute(self, blackboard: Blackboard):
pass
class PatternMatcher(KnowledgeSource):
def can_contribute(self, blackboard: Blackboard) -> bool:
return len(blackboard.hypotheses) < 3
def contribute(self, blackboard: Blackboard):
bb = blackboard
bb.add_hypothesis(Hypothesis(
content="Patrón detectado: probablemente función",
confidence=Confidence.MEDIUM,
source=self.name
))
class SemanticAnalyzer(KnowledgeSource):
def can_contribute(self, blackboard: Blackboard) -> bool:
return any(h.source == "PatternMatcher" for h in blackboard.hypotheses)
def contribute(self, blackboard: Blackboard):
bb = blackboard
bb.add_hypothesis(Hypothesis(
content="Análisis semántico: intención de usuario es ejecución de query",
confidence=Confidence.HIGH,
source=self.name
))
class ConfidenceEvaluator(KnowledgeSource):
def can_contribute(self, blackboard: Blackboard) -> bool:
best = blackboard.get_best_hypothesis()
return best is not None and best.confidence == Confidence.HIGH
def contribute(self, blackboard: Blackboard):
best = blackboard.get_best_hypothesis()
if best:
blackboard.set_solution(best.content)
class Controller:
def __init__(self, blackboard: Blackboard, sources: List[KnowledgeSource]):
self.blackboard = blackboard
self.sources = sources
def solve(self, max_iterations: int = 10):
for _ in range(max_iterations):
if self.blackboard.complete:
break
for source in self.sources:
if source.can_contribute(self.blackboard):
source.contribute(self.blackboard)
break
# Uso
bb = Blackboard()
sources = [
PatternMatcher("PatternMatcher"),
SemanticAnalyzer("SemanticAnalyzer"),
ConfidenceEvaluator("ConfidenceEvaluator"),
]
controller = Controller(bb, sources)
controller.solve()
print(f"Solución: {bb.final_solution}")
print(f"Todas las hipótesis: {[h.content for h in bb.hypotheses]}")
Java
import java.util.*;
enum Confidence { LOW, MEDIUM, HIGH }
record Hypothesis(String content, Confidence confidence, String source) {}
class Blackboard {
private final List<Hypothesis> hypotheses = new ArrayList<>();
private String finalSolution;
private boolean complete = false;
public void addHypothesis(Hypothesis h) { hypotheses.add(h); }
public List<Hypothesis> getHypotheses() { return hypotheses; }
public boolean isComplete() { return complete; }
public Hypothesis getBestHypothesis() {
return hypotheses.stream()
.max(Comparator.comparingInt(h -> h.confidence().ordinal()))
.orElse(null);
}
public void setSolution(String solution) {
this.finalSolution = solution;
this.complete = true;
}
public String getFinalSolution() { return finalSolution; }
}
abstract class KnowledgeSource {
protected final String name;
public KnowledgeSource(String name) { this.name = name; }
public String getName() { return name; }
public abstract boolean canContribute(Blackboard bb);
public abstract void contribute(Blackboard bb);
}
class PatternMatcher extends KnowledgeSource {
public PatternMatcher() { super("PatternMatcher"); }
public boolean canContribute(Blackboard bb) { return bb.getHypotheses().size() < 3; }
public void contribute(Blackboard bb) {
bb.addHypothesis(new Hypothesis("Patrón detectado: probablemente función", Confidence.MEDIUM, name));
}
}
class SemanticAnalyzer extends KnowledgeSource {
public SemanticAnalyzer() { super("SemanticAnalyzer"); }
public boolean canContribute(Blackboard bb) {
return bb.getHypotheses().stream().anyMatch(h -> h.source().equals("PatternMatcher"));
}
public void contribute(Blackboard bb) {
bb.addHypothesis(new Hypothesis("Semántica: intención de usuario es ejecución de consulta", Confidence.HIGH, name));
}
}
class ConfidenceEvaluator extends KnowledgeSource {
public ConfidenceEvaluator() { super("ConfidenceEvaluator"); }
public boolean canContribute(Blackboard bb) {
Hypothesis best = bb.getBestHypothesis();
return best != null && best.confidence() == Confidence.HIGH;
}
public void contribute(Blackboard bb) {
Hypothesis best = bb.getBestHypothesis();
if (best != null) bb.setSolution(best.content());
}
}
class Controller {
private final Blackboard blackboard;
private final List<KnowledgeSource> sources;
public Controller(Blackboard blackboard, List<KnowledgeSource> sources) {
this.blackboard = blackboard;
this.sources = sources;
}
public void solve(int maxIterations) {
for (int i = 0; i < maxIterations && !blackboard.isComplete(); i++) {
for (KnowledgeSource source : sources) {
if (source.canContribute(blackboard)) {
source.contribute(blackboard);
break;
}
}
}
}
}
// Uso
Blackboard bb = new Blackboard();
List<KnowledgeSource> sources = List.of(
new PatternMatcher(),
new SemanticAnalyzer(),
new ConfidenceEvaluator()
);
Controller controller = new Controller(bb, sources);
controller.solve(10);
System.out.println("Solución: " + bb.getFinalSolution());
JavaScript
const Confidence = {
LOW: 1,
MEDIUM: 2,
HIGH: 3,
};
class Blackboard {
constructor() {
this.hypotheses = [];
this.finalSolution = null;
this.complete = false;
}
addHypothesis(hypothesis) {
this.hypotheses.push(hypothesis);
}
getBestHypothesis() {
if (this.hypotheses.length === 0) return null;
return this.hypotheses.reduce((best, h) =>
h.confidence > best.confidence ? h : best
);
}
setSolution(solution) {
this.finalSolution = solution;
this.complete = true;
}
}
class KnowledgeSource {
constructor(name) {
this.name = name;
}
canContribute(blackboard) {
throw new Error('Must implement canContribute');
}
contribute(blackboard) {
throw new Error('Must implement contribute');
}
}
class PatternMatcher extends KnowledgeSource {
constructor() { super('PatternMatcher'); }
canContribute(bb) { return bb.hypotheses.length < 3; }
contribute(bb) {
bb.addHypothesis({
content: 'Patrón detectado: probablemente función',
confidence: Confidence.MEDIUM,
source: this.name,
});
}
}
class SemanticAnalyzer extends KnowledgeSource {
constructor() { super('SemanticAnalyzer'); }
canContribute(bb) {
return bb.hypotheses.some(h => h.source === 'PatternMatcher');
}
contribute(bb) {
bb.addHypothesis({
content: 'Análisis semántico: intención de usuario es ejecución de query',
confidence: Confidence.HIGH,
source: this.name,
});
}
}
class ConfidenceEvaluator extends KnowledgeSource {
constructor() { super('ConfidenceEvaluator'); }
canContribute(bb) {
const best = bb.getBestHypothesis();
return best && best.confidence === Confidence.HIGH;
}
contribute(bb) {
const best = bb.getBestHypothesis();
if (best) bb.setSolution(best.content);
}
}
class Controller {
constructor(blackboard, sources) {
this.blackboard = blackboard;
this.sources = sources;
}
solve(maxIterations = 10) {
for (let i = 0; i < maxIterations && !this.blackboard.complete; i++) {
for (const source of this.sources) {
if (source.canContribute(this.blackboard)) {
source.contribute(this.blackboard);
break;
}
}
}
}
}
// Uso
const bb = new Blackboard();
const sources = [
new PatternMatcher(),
new SemanticAnalyzer(),
new ConfidenceEvaluator(),
];
const controller = new Controller(bb, sources);
controller.solve();
console.log('Solución:', bb.finalSolution);
Explicación
Tres componentes forman el Patrón Blackboard:
- Blackboard: la estructura de datos compartida que mantiene el estado del problema y las hipótesis acumuladas
- Knowledge Sources: módulos independientes que leen el blackboard, comprueban si sus condiciones aplican, y publican soluciones parciales
- Controller: el bucle que elige qué fuente debe actuar a continuación
Las fuentes nunca hablan entre sí; solo leen y escriben en el blackboard. ¿Quieres extender el sistema? Suelta una fuente nueva — nada más necesita cambiar.
Como contraste, el patrón Mediator centraliza la coordinación entre objetos conocidos en lugar de difundir mediante estado compartido, y el patrón Event Bus enruta eventos explícitos en lugar de dejar que las fuentes inspeccionen hipótesis compartidas.
Variantes
| Variante | Estrategia de Control | Caso de Uso |
|---|---|---|
| Oportunista | Cualquier fuente contribuye cuando se cumplen sus condiciones | Sistemas simples y descentralizados |
| Basada en prioridad | El controller elige la fuente elegible de mayor prioridad | Sistemas complejos con muchas fuentes |
| Dirigida por eventos | Las fuentes reaccionan a cambios del blackboard | Sistemas de IA reactivos |
| Jerárquica | Blackboards en distintos niveles de abstracción | Resolución de problemas por etapas |
Lo que funciona
- Mantén las fuentes independientes. Si las fuentes se llaman directamente entre sí, has construido un mediator, no un blackboard.
- Dale una cola de prioridad al controller. Recorrer las fuentes en orden desperdicia ciclos — elige la que pinte más prometedora.
- Puntúa la confianza de cada hipótesis. Los niveles de confianza le dan al controller algo real con lo que decidir.
- Limita las iteraciones. No hay garantía de convergencia, así que necesitas una vía de escape.
- Registra cada contribución. Cuando un sistema blackboard falla, la traza de contribuciones es cómo lo encuentras.
Una versión ejecutable de este patrón — tres knowledge sources clasificando texto sobre un blackboard compartido — está en el repositorio companion.
Errores Comunes
- Acoplamiento fuerte entre fuentes. La comunicación directa anula el propósito del patrón.
- Sin condiciones de terminación. Sin tope de iteraciones ni check de completitud, el bucle simplemente no para.
- Sobreescribir hipótesis de alta confianza. Una contribución nueva no debería reemplazar ciegamente a una mejor.
- Controller monolítico. Un controller que hace todo se vuelve el cuello de botella y el punto único de fallo.
- Ignorar las condiciones de carrera. Las implementaciones multihilo necesitan acceso sincronizado al blackboard.
Ejemplos del Mundo Real
Sistemas de Reconocimiento de Voz
El reconocimiento de voz es la aplicación clásica del blackboard: modelos acústicos, modelos de lenguaje, diccionarios de pronunciación y analizadores contextuales publican hipótesis sobre lo que se dijo, y el sistema converge en la transcripción más probable.
Pipelines de Reconocimiento de Imágenes
En visión por computadora, detectores de bordes, reconocedores de formas y clasificadores semánticos publican interpretaciones parciales de una imagen en un tablero compartido — la misma idea con otra ropa.
Solvers de Optimización
Los solvers de restricciones usados en scheduling y routing ejecutan varias heurísticas que refinan iterativamente un espacio de solución compartido — blackboard en todo menos en el nombre.
Preguntas frecuentes
¿Cuál es la diferencia entre los patrones Blackboard y Pipeline?
Los pipelines mueven datos de forma lineal, etapa a etapa — cada paso solo ve la salida del anterior. Una fuente del blackboard contribuye cuando tiene algo que aportar, leyendo las hipótesis que haya en el tablero — el orden no existe.
¿Cómo decide el controller qué fuente activar?
Normalmente por orden de prioridad, umbrales de confianza, o pujas estilo subasta donde cada fuente puntúa su propia capacidad de contribuir.
¿Es el patrón Blackboard adecuado para sistemas de tiempo real?
Encaja mal. La sobrecarga de coordinación hace la latencia impredecible, así que los sistemas de tiempo real suelen preferir pipelines determinísticos o máquinas de estado.
¿Puedo usar Blackboard en un proyecto pequeño?
Puedes, pero rara vez compensa — con pocos componentes la sobrecarga de coordinación pesa más que la flexibilidad. Empieza simple e introdúcelo cuando el problema de verdad necesite hipótesis que compiten.
¿En qué se diferencia Blackboard de Mediator?
Mediator centraliza la comunicación entre objetos que se conocen entre sí. Blackboard los desacopla mediante estado compartido y contribución oportunista — las fuentes ni siquiera saben que existen otras.
Recursos Relacionados
Patrón Mediator
Define un objeto que encapsula cómo interactúa un conjunto de objetos. Un patrón de comportamiento para reducir dependencias caóticas.
PatternPatrón Observer
Define un mecanismo de suscripción para notificar a múltiples objetos sobre eventos. Patrón de diseño conductual para comunicación basada en eventos.
PatternPatrón Event Bus
Desacopla componentes rutando eventos a través de un bus central. Un patrón behavioral para comunicación entre módulos con acoplamiento mínimo.
PatternPatron de Programador-Agente-Supervisor
Coordina la programacion de trabajos resilientes con un supervisor que monitorea agentes, reinicia fallas y gestiona el ciclo de vida.
PatternPatrón Agent Tool Selection
Selecciona dinamicamente que herramientas puede usar un agente LLM segun el contexto de la tarea. Reduce tokens y mejora la calidad de decisiones.
PatternPatrón Strategy
Define una familia de algoritmos, encapsula cada uno y los hace intercambiables. Patrón de diseño conductual para selección flexible de comportamiento.