prompt_orchestrator
Python module for structured prompt orchestration with:
- static/semi-stable/dynamic prompt layout
- configurable summary LLM with provider selection
- TTL cache backends
- optional RAG providers
- safety checks (config-driven grouped threats, weighted groups, bilingual patterns, contradiction pairs)
- prompt efficiency analyzer
- token counting with tiktoken
- centralized mutable config (Pydantic)
- one-call orchestrator bootstrap from config store
Install
pip install -e .
For development and tests:
pip install -e .[dev]
Install with optional OpenTelemetry support:
pip install -e .[otel]
Optional OpenTelemetry + SigNoz
OpenTelemetry is optional. If not installed or not enabled, PromptOrchestrator works as before.
SigNoz is expected to run separately (for example, official SigNoz Docker deployment on http://localhost:8080).
Enable OTel (host runtime):
ENABLE_OTEL=true
OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317
OTEL_SERVICE_NAME=prompt-orchestrator
OTEL_SERVICE_NAMESPACE=prompt-stack
OTEL_DEPLOYMENT_ENVIRONMENT=dev
Required/optional flags summary:
- Start telemetry export (required): set
ENABLE_OTEL=true - Stop telemetry export (required): set
ENABLE_OTEL=false - OTLP destination (optional, used when enabled):
OTEL_EXPORTER_OTLP_ENDPOINT - Resource labels (optional):
OTEL_SERVICE_NAME,OTEL_SERVICE_NAMESPACE,OTEL_DEPLOYMENT_ENVIRONMENT,OTEL_SERVICE_VERSION
Run local OTel Collector (1 additional container):
docker compose -f docker-compose.otel.yml up -d
Disable OTel (host runtime):
ENABLE_OTEL=false
Stop local OTel Collector:
docker compose -f docker-compose.otel.yml down
Files used:
docker-compose.otel.ymlobservability/otel-collector-config.yaml
Default endpoints:
- SigNoz UI (external):
http://localhost:8080 - OTLP gRPC ingest (local collector):
http://localhost:4317 - OTLP HTTP ingest (local collector):
http://localhost:4318
Exposed telemetry (when enabled):
| Telemetry signal name | Description |
|---|---|
prompt_orchestrator.build_for_request |
Trace span for one prompt build request. Includes attribute session.id. |
prompt_build_requests_total |
Counter of prompt build attempts. Attributes include operation=build_for_request and status (ok/error). |
prompt_errors_total |
Counter of errors by operation and error type. Attributes include operation and error.type. |
prompt_build_latency_ms |
Histogram of prompt build latency in milliseconds. |
prompt_total_tokens |
Histogram of total token count in the built prompt payload. |
prompt_total_chars |
Histogram of total character count in the built prompt payload. |
prompt_rag_chunks_count |
Histogram of retrieved RAG chunks used in the prompt. |
prompt_warnings_count |
Histogram of analyzer warnings count per build. |
prompt_safety_events_total |
Counter of safety events. Attributes include severity and status. |
prompt_summary_calls_total |
Counter of summary calls. Attributes include operation=summary, provider, and status. |
prompt_summary_latency_ms |
Histogram of summary call latency in milliseconds. |
prompt.error operation={operation} error_type={error_type} |
OTLP log message emitted on errors (for example in build_for_request or summary). |
Dashboard template blueprint:
observability/signoz-dashboard-prompt-orchestrator.yaml
Use it as a panel/query blueprint in SigNoz to create a dashboard for prompt build latency, token pressure, RAG payload size, safety events, summary latency, logs, and traces.
Configuration Models
PromptConfig: static prompt structureOrchestratorSettings: runtime limits and behaviorSummaryLLMConfig: summary provider and model settingsSafetyLLMConfig: optional LLM-based safety analysis (provider/model selectable)ModuleConfig: full module config in one objectConfigStore: mutable config holder (get,set_config,as_dict)
Enterprise Prompt Controls
PromptConfig now includes enterprise-level controls for answer governance:
response_language:ru | en | auto(default:ru)output_contract: strict output contract configuration- default
mode="json_markdown" - default
strict=True - default schema hint for enterprise review payload
- default
tool_calling_policy: tool usage policy for downstream tool-aware agentsmode:allow | deny | allowlist(default:allow)max_calls(default:8)allowed_toolsfor allowlist mode- JSON args/result acknowledgement guard flags
Example:
from prompt_orchestrator import PromptConfig, OutputContractConfig, ToolCallingPolicyConfig
cfg = PromptConfig(
system_prompt="Вы корпоративный ассистент.",
role="Ревьюер документации",
task="Сформируйте проверяемый ответ на русском.",
constraints=["Не придумывайте факты", "Всегда давайте ссылки/цитаты"],
output_format="Markdown",
examples=[],
response_language="ru",
output_contract=OutputContractConfig(
mode="json_markdown",
strict=True,
schema_hint='{"summary":"str","findings":["str"],"risks":["str"],"actions":["str"],"citations":["str"]}',
),
tool_calling_policy=ToolCallingPolicyConfig(
mode="allow",
max_calls=8,
),
)
Safety Engine
The safety layer is configured from prompt_orchestrator/safety/threats.json. The catalog is grouped by threat family, and each family has its own weight so the final severity is still computed by the maximum matched threat score.
What changed:
- threat families are defined in
threats.jsonand loaded at runtime - regular lexical rules live under
patterns - contradiction rules live under
contradictionsand are matched as pairs - each family can include English and Russian analogs for the same threat family
- duplicate patterns were removed from the catalog
- each matched rule keeps its threat code in the report
SafetyReport now includes:
issues: flat list of matched safety issuesthreat_groups: grouped report by threat familyseverity: overall severity (none,low,medium,high)threat_score: weighted maximum score used for the final severitysanitized_prompt: optional rewritten prompt when auto rewrite is enabledllm_used: whether LLM safety analyzer was appliedllm_provider/llm_model: provider/model used for LLM safety passllm_score/llm_severity: raw LLM risk output before final mergellm_reasoning: short textual explanation returned by LLM checker
Optional LLM Safety Layer
You can enable an additional LLM-based safety pass on top of lexical rules.
Default config:
security_checks_llm_enabled=False(opt-in)provider="ollama"model="qwen2.5:3b"(multilingual, works with Russian prompts)security_checks_llm_merge_strategy="max"(take max risk between lexical + LLM)security_checks_llm_fail_mode="open"(fallback to lexical-only if LLM check fails)security_checks_llm_auto_pull_ollama_model=True(if model is missing, it is pulled from Ollama)
Important behavior:
- LLM provider clients are initialized lazily.
- If
security_checks_llm_enabled=False, no LLM provider client is created.
Example:
from prompt_orchestrator import ModuleConfig, SafetyLLMConfig
cfg = ModuleConfig(
prompt=..., # PromptConfig
safety_llm=SafetyLLMConfig(
security_checks_llm_enabled=True,
provider="ollama", # or "openai" / "custom" / "none"
model="qwen2.5:3b",
security_checks_llm_merge_strategy="max", # max | llm_only | heuristic_only
security_checks_llm_fail_mode="open", # open | closed
),
)
Each grouped report includes the threat family name, the number of matches, the matched codes, and the family weight. Use result.safety.grouped_summary or result.safety.model_dump() to inspect the grouped output.
OrchestratorSettings.debug_mode
By default, section headers (=== STATIC PART (CACHE-FRIENDLY) ===, etc.) are excluded from the final prompt sent to LLMs to save tokens.
Enable debug_mode=True to include section headers for:
- Debugging and development
- Understanding prompt structure during testing
- Console/log output inspection
settings = OrchestratorSettings(
debug_mode=True, # Enables section headers in output
)
In simulations, use --debug flag:
python simulations/console_pipeline_test.py # Prompts for debug mode
python simulations/conversation_simulation_test.py --debug # Enable debug headers
Security rewrite toggle in OrchestratorSettings:
security_checks_auto_rewrite=True: rewrite prompt when safety severity ismediumorhigh- Legacy alias
safety_auto_rewriteis still accepted for backward compatibility
Supported Summary Providers
none: deterministic local fallback summarizationopenai: OpenAI viaopenaiSDKollama: local Ollama endpoint via/api/generatecustom: bring your own client implementinggenerate(prompt, model, max_tokens, temperature)
Integration with RagflowOrchestrator
PromptOrchestrator can work directly with RagflowOrchestrator as a retrieval backend.
Why this pairing works well:
- PromptOrchestrator controls prompt layout, context compaction, safety checks, and token budgets.
- RagflowOrchestrator handles indexing, embedding, and retrieval from vector storage.
- Both projects use a compatible
DocChunkshape (id,content,score,metadata).
Option 1: Use RagflowOrchestrator compatibility adapter (recommended)
RagflowOrchestrator includes PromptStyleRAGProviderAdapter, which exposes the exact interface PromptOrchestrator expects (retrieve(query, limit)).
from prompt_orchestrator import (
LocalTTLCacheBackend,
OrchestratorSettings,
PromptConfig,
PromptContextManager,
PromptOrchestrator,
SummaryLLM,
)
from ragflow_orchestrator import HashEmbedder, create_provider
from ragflow_orchestrator.rag import PromptStyleRAGProviderAdapter
# RagflowOrchestrator side: provider + embedder
provider = create_provider(kind="sqlite", db_path="rag.db", table="chunks")
embedder = HashEmbedder(dimensions=256)
# Adapter gives PromptOrchestrator-compatible retrieve(query, limit)
rag_provider = PromptStyleRAGProviderAdapter(provider=provider, embedder=embedder)
config = PromptConfig(
system_prompt="You are a grounded assistant.",
role="Engineer",
task="Answer using retrieved context.",
constraints=["Cite retrieved facts", "Avoid unsupported claims"],
output_format="Markdown",
examples=[],
)
settings = OrchestratorSettings(use_rag_default=True, rag_limit=4)
cache = LocalTTLCacheBackend(default_ttl_seconds=settings.cache_ttl_seconds)
context_manager = PromptContextManager(cache, settings, SummaryLLM())
orchestrator = PromptOrchestrator(
config=config,
context_manager=context_manager,
rag_provider=rag_provider,
settings=settings,
)
result = orchestrator.build_for_request(
session_id="rag-integration-demo",
user_message="How does deduplication work in our retrieval pipeline?",
use_rag=True,
)
print(result.prompt)
Option 2: Wrap RAGOrchestrator.search(...) in a thin adapter
If you already use a full RAGOrchestrator pipeline (ingest + search), expose it as a RAGProvider for PromptOrchestrator:
from prompt_orchestrator.rag.base import RAGProvider
from prompt_orchestrator.context.state import DocChunk
from rag_orchestrator import RAGOrchestrator
class RagOrchestratorProvider(RAGProvider):
def __init__(self, orchestrator: RAGOrchestrator) -> None:
self._orchestrator = orchestrator
def retrieve(self, query: str, limit: int) -> list[DocChunk]:
rows = self._orchestrator.search(query_text=query, top_k=limit)
return [
DocChunk(
id=row.chunk.id,
content=row.chunk.text,
score=row.score,
metadata={str(k): str(v) for k, v in row.chunk.metadata.items()},
)
for row in rows
]
Use this adapter as rag_provider in PromptOrchestrator(...) and set use_rag=True when building requests.
Simulations Folder
Simulation assets are located in simulations:
- simulations/console_pipeline_test.py: interactive console runner for manual checks
- simulations/conversation_simulation_test.py: scripted multi-turn simulation with prompt/STATS/SAFETY output
- simulations/test_turns.json: regular conversation turns for context-window and compaction checks
- simulations/safety_injection_turns.json: unsafe/injection turns for SAFETY trigger checks
- simulations/conversation_simulation_test.log: output log from last simulation run (overwritten on each run)
How to work with simulations:
# Interactive pipeline (manual typing)
python simulations/console_pipeline_test.py
# Scripted simulation from JSON turns
python simulations/conversation_simulation_test.py
# Include unsafe/injection scenarios
python simulations/conversation_simulation_test.py --include-safety
# Run without RAG and cap turns
python simulations/conversation_simulation_test.py --no-rag --max-turns 5
Example 1: Manual Wiring (Local, No RAG)
from prompt_orchestrator import (
LocalTTLCacheBackend,
NoRAGProvider,
OrchestratorSettings,
PromptConfig,
PromptContextManager,
PromptOrchestrator,
SummaryLLM,
)
config = PromptConfig(
system_prompt="You are a helpful assistant.",
role="Senior Analyst",
task="Answer user questions precisely.",
constraints=["Do not hallucinate", "Use concise style"],
output_format="Markdown",
examples=["Q: 2+2? A: 4"],
)
settings = OrchestratorSettings(
max_prompt_chars=12000,
max_prompt_tokens=3000,
recent_messages_limit=10,
cache_ttl_seconds=900,
rag_limit=3,
)
cache = LocalTTLCacheBackend(default_ttl_seconds=settings.cache_ttl_seconds)
summary_llm = SummaryLLM()
context_manager = PromptContextManager(cache, settings, summary_llm)
orchestrator = PromptOrchestrator(
config=config,
context_manager=context_manager,
rag_provider=NoRAGProvider(),
settings=settings,
)
result = orchestrator.build_for_request(
session_id="demo-session",
user_message="Explain how TTL helps prompt caching",
use_rag=False,
)
print(result.prompt)
print(result.stats.model_dump())
print(result.safety.model_dump())
Example 2: Centralized Config + Factory (One-Call Bootstrap)
from prompt_orchestrator import (
ConfigStore,
ModuleConfig,
OrchestratorSettings,
PromptConfig,
SummaryLLMConfig,
PromptOrchestratorFactory,
)
full_config = ModuleConfig(
prompt=PromptConfig(
system_prompt="You are a helpful assistant.",
role="Engineer",
task="Answer clearly",
constraints=["No hallucinations"],
output_format="Markdown",
examples=[],
),
settings=OrchestratorSettings(max_prompt_tokens=3000),
summary_llm=SummaryLLMConfig(provider="openai", model="gpt-4o-mini"),
)
store = ConfigStore(full_config)
model_name = store.get("summary_llm.model")
orchestrator = PromptOrchestratorFactory.from_config_store(store)
result = orchestrator.build_for_request(
session_id="factory-demo",
user_message="What is TTL cache?",
use_rag=False,
)
Example 3: OpenAI Summary Provider
from prompt_orchestrator import (
ConfigStore,
ModuleConfig,
OpenAIConfig,
OrchestratorSettings,
PromptConfig,
PromptOrchestratorFactory,
SummaryLLMConfig,
)
cfg = ModuleConfig(
prompt=PromptConfig(
system_prompt="You are a concise assistant.",
role="Tech Writer",
task="Summarize conversation state and answer user request.",
constraints=["No speculative claims"],
output_format="Markdown",
examples=[],
),
settings=OrchestratorSettings(
max_prompt_tokens=2500,
token_model="gpt-4o-mini",
),
summary_llm=SummaryLLMConfig(
provider="openai",
model="gpt-4o-mini",
openai=OpenAIConfig(
api_key="YOUR_OPENAI_API_KEY",
base_url=None,
organization=None,
),
),
)
store = ConfigStore(cfg)
orchestrator = PromptOrchestratorFactory.from_config_store(store)
response = orchestrator.build_for_request(
session_id="openai-summary",
user_message="Please summarize previous decisions and next actions",
use_rag=False,
)
print(response.stats.total_tokens)
Token Counting (tiktoken)
- Prompt length checks use tiktoken-based counting
- Configure tokenizer via
OrchestratorSettings.token_modelandOrchestratorSettings.token_encoding - Limit fitting in
PromptContextManager.ensure_fits_limittrims sections to satisfy both char and token budgets
Running Tests
pytest -q
Metadata
Release files for prompt-orchestrator 0.1.9
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| prompt_orchestrator-0.1.9.tar.gz | 38.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| prompt_orchestrator-0.1.9-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 78.0 kB
Release files / prompt_orchestrator-0.1.9.tar.gz
| Download URL | prompt_orchestrator-0.1.9.tar.gz |
|---|---|
| Size | 38.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3cb5e8aeca83e607eff333ea74042f944432667f6146be5dc5e22ec21a69ff58
|
|
BLAKE2b-256 checksum How to use checksums |
81db83682de8786877eb3665dd0f3c988ad937cef507afb740477412136fee06
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 24, 2026.
Transparency logRelease files / prompt_orchestrator-0.1.9-py3-none-any.whl
| Download URL | prompt_orchestrator-0.1.9-py3-none-any.whl |
|---|---|
| Size | 39.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
21b26f18df6a360ed0982773158e8d0020dca4f50737e0ae2b4efc83cc1580dd
|
|
BLAKE2b-256 checksum How to use checksums |
fbe25c256a7f8306570575023051ff431fdd5462c545b18dbc24873bce4cb5a0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 24, 2026.
Transparency log