Send one prompt to one or more LLM providers across a spread of sampling temperatures — several responses, side by side, in your terminal.
The same prompt is dispatched in parallel to a set of personas. Each persona pairs a temperature with a backend provider, so you can compare how temperature shifts a model from precise and terse to vivid and exploratory — and, when you configure more than one provider, how Gemini, Claude, and OpenAI answer the same prompt differently. Each response is rendered as its own panel.
The name is a metaphor: one beam — the prompt — entering a prism becomes multiple colours, each a different answer.
$ python main.py "What is the capital of France?"
╭─ gemini (creative, temp=0.9) ──────────────────────╮
│ The capital is Paris, a city famed for its │
│ riverside elegance and centuries of layered │
│ history beneath every street corner. │
│ │
│ ⏱ 1.2s · 📊 142 tokens │
╰────────────────────────────────────────────────────╯
╭─ gemini (balanced, temp=0.5) ──────────────────────╮
│ Paris is the capital of France. │
│ │
│ ⏱ 0.8s · 📊 98 tokens │
╰────────────────────────────────────────────────────╯
╭─ gemini (precise, temp=0.1) ───────────────────────╮
│ Paris. │
│ │
│ ⏱ 0.6s · 📊 12 tokens │
╰────────────────────────────────────────────────────╯
When a persona fails (rate limit, timeout, auth error, etc.) its panel turns red and the error category is shown in place of the text; the other personas still complete and render.
Pass --json to emit a JSON array instead of panels, for piping into other tools:
python main.py "What is the capital of France?" --json | jq '.[] | {persona: .persona_name, kind}'Each element carries a kind discriminator ("success" or "failure") so consumers can branch on one field.
Personas are configurable via a YAML file passed with --personas. Copy personas.example.yaml and edit it:
personas:
- name: creative
temperature: 0.9
system_prompt: "Lean into vivid, exploratory framings."
- name: precise
temperature: 0.1python main.py "Explain recursion." --personas personas.yamlEach persona needs a unique name and a temperature in [0.0, 2.0]; system_prompt is optional. Without --personas, the built-in creative / balanced / precise set is used.
A persona can name a provider — gemini (default), claude, or openai. With the matching API keys set, a single prompt fans out across backends at once:
personas:
- name: gemini-take
temperature: 0.7
provider: gemini
- name: claude-take
temperature: 0.7
provider: claude
- name: openai-take
temperature: 0.7
provider: openaiA persona whose provider has no configured key renders as a ProviderUnavailable panel rather than aborting the run.
- Python 3.12 or newer
- At least one provider API key:
- Gemini — https://aistudio.google.com
- Anthropic (Claude) — https://console.anthropic.com
- OpenAI — https://platform.openai.com
git clone https://github.com/Ohualtex/Prismatic.git
cd Prismatic
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtcp .env.example .env
# Open .env and set at least one of GEMINI_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEYpython main.py "What is the capital of France?"Each persona fires in parallel via asyncio.gather. Every provider client (GeminiClient, ClaudeClient, OpenAIClient) satisfies a common LLMClient protocol, and the orchestrator dispatches each persona to its provider through a registry — so adding a provider needs no change to the orchestrator. Each call is wrapped in a retry loop with jittered exponential backoff for transient failures (429, 5xx, timeout); permanent failures (4xx, auth) fail fast. Results are gathered in the original persona order regardless of completion order, then rendered — green panels for success, red for failure.
Both successful and failed responses are first-class data: the orchestrator returns a list of ModelResponse values where every element is either a ModelSuccess or a ModelFailure, never an exception. Callers branch on response type, not try/except.
pip install -r requirements-dev.txt
# Run the test suite (fully mocked — no API calls)
pytest
# Format, lint, type-check
ruff format .
ruff check .
mypy --strict .Tek bir prompt'u bir veya daha fazla LLM sağlayıcısına, bir dizi sıcaklık (temperature) değeriyle gönderin — birkaç cevap, yan yana, terminalinizde.
Aynı prompt bir persona kümesine paralel olarak dağıtılır. Her persona bir sıcaklığı bir arka uç sağlayıcısıyla eşler; böylece temperature'ün bir modeli kesin ve özlü olmaktan canlı ve keşifçi olmaya nasıl kaydırdığını — ve birden fazla sağlayıcı yapılandırdığınızda Gemini, Claude ve OpenAI'nin aynı prompt'a nasıl farklı yanıt verdiğini — karşılaştırabilirsiniz. Her cevap kendi paneline render edilir.
İsim bir metafor: tek bir ışın — yani prompt — prizmadan geçince birden fazla renge, yani farklı cevaplara ayrılır.
Bir persona başarısız olduğunda (rate limit, timeout, auth hatası vb.) paneli kırmızıya döner ve metin yerine hata kategorisi gösterilir; diğer personalar yine de tamamlanır ve render edilir.
Paneller yerine JSON dizisi üretmek için --json geçin; başka araçlara aktarmak (pipe) için:
python main.py "Fransa'nın başkenti nedir?" --json | jq '.[] | {persona: .persona_name, kind}'Her eleman bir kind ayırıcısı ("success" veya "failure") taşır; böylece tüketiciler tek alan üzerinden dallanabilir.
Personalar --personas ile geçirilen bir YAML dosyasıyla yapılandırılabilir. personas.example.yaml'ı kopyalayıp düzenleyin:
personas:
- name: creative
temperature: 0.9
system_prompt: "Lean into vivid, exploratory framings."
- name: precise
temperature: 0.1python main.py "Özyinelemeyi açıkla." --personas personas.yamlHer persona benzersiz bir name ve [0.0, 2.0] aralığında bir temperature gerektirir; system_prompt opsiyoneldir. --personas olmadan yerleşik creative / balanced / precise kümesi kullanılır.
Bir persona bir provider adlandırabilir — gemini (varsayılan), claude veya openai. Eşleşen API anahtarları ayarlıyken, tek bir prompt aynı anda arka uçlara dağılır:
personas:
- name: gemini-take
temperature: 0.7
provider: gemini
- name: claude-take
temperature: 0.7
provider: claude
- name: openai-take
temperature: 0.7
provider: openaiSağlayıcısının yapılandırılmış anahtarı olmayan bir persona, çalışmayı iptal etmek yerine ProviderUnavailable paneli olarak render edilir.
- Python 3.12 veya üstü
- En az bir sağlayıcı API anahtarı:
- Gemini — https://aistudio.google.com
- Anthropic (Claude) — https://console.anthropic.com
- OpenAI — https://platform.openai.com
git clone https://github.com/Ohualtex/Prismatic.git
cd Prismatic
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtcp .env.example .env
# .env'yi açın ve GEMINI_API_KEY, ANTHROPIC_API_KEY, OPENAI_API_KEY'den en az birini ayarlayınpython main.py "Fransa'nın başkenti nedir?"Her persona asyncio.gather ile paralel çalışır. Her sağlayıcı client'ı (GeminiClient, ClaudeClient, OpenAIClient) ortak bir LLMClient protokolünü karşılar ve orchestrator her personayı bir registry aracılığıyla sağlayıcısına yönlendirir — böylece sağlayıcı eklemek orchestrator'da değişiklik gerektirmez. Her çağrı, geçici hatalar (429, 5xx, timeout) için jitter'lı üstel backoff retry döngüsüyle sarılır; kalıcı hatalar (4xx, auth) hızlıca başarısız olur. Sonuçlar, tamamlanma sırasından bağımsız olarak orijinal persona sırasında toplanır ve render edilir — başarı için yeşil, hata için kırmızı paneller.
Başarılı ve başarısız cevaplar birinci sınıf veridir: orchestrator bir ModelResponse listesi döner — her eleman ya ModelSuccess ya ModelFailure, asla exception değil. Çağıran taraf try/except yerine cevap tipi üzerinden dallanır.
pip install -r requirements-dev.txt
# Test suite'i çalıştır (tamamen mock'lu — API çağrısı yok)
pytest
# Format, lint, tip kontrolü
ruff format .
ruff check .
mypy --strict .